Skip to content

Commit 10681da

Browse files
✨ Feature DependencyDiff (Version 0 Part 2) (#2046)
* temp * Update dependencies.go * Update errors.go * Update scorecard_results.go * Update vulnerabilities.go * save * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp0713-1 * temp0713-2 * temp0713-3 * temp0713-4 * temp0713-4 * temp0713-5 * temp0713-6 * temp0713-7 * temp0713-8 * temp0713-9 * temp0713-10 * temp0713-11 * temp0713-12 * 1 * temp * temp * temp * temp * temp * temp * temp * temp * save * save * save * final_commit_before_merge
1 parent dd8fbc0 commit 10681da

File tree

5 files changed

+525
-10
lines changed

5 files changed

+525
-10
lines changed

dependencydiff/dependencydiff.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2022 Security Scorecard Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dependencydiff
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"path"
21+
22+
"github.com/ossf/scorecard/v4/checker"
23+
"github.com/ossf/scorecard/v4/checks"
24+
"github.com/ossf/scorecard/v4/clients"
25+
"github.com/ossf/scorecard/v4/clients/githubrepo"
26+
sce "github.com/ossf/scorecard/v4/errors"
27+
"github.com/ossf/scorecard/v4/log"
28+
"github.com/ossf/scorecard/v4/pkg"
29+
"github.com/ossf/scorecard/v4/policy"
30+
)
31+
32+
// Depdiff is the exported name for dependency-diff.
33+
const Depdiff = "Dependency-diff"
34+
35+
type dependencydiffContext struct {
36+
logger *log.Logger
37+
ownerName, repoName, baseSHA, headSHA string
38+
ctx context.Context
39+
ghRepo clients.Repo
40+
ghRepoClient clients.RepoClient
41+
ossFuzzClient clients.RepoClient
42+
vulnsClient clients.VulnerabilitiesClient
43+
ciiClient clients.CIIBestPracticesClient
44+
changeTypesToCheck map[pkg.ChangeType]bool
45+
checkNamesToRun []string
46+
dependencydiffs []dependency
47+
results []pkg.DependencyCheckResult
48+
}
49+
50+
// GetDependencyDiffResults gets dependency changes between two given code commits BASE and HEAD
51+
// along with the Scorecard check results of the dependencies, and returns a slice of DependencyCheckResult.
52+
// TO use this API, an access token must be set following https://github.com/ossf/scorecard#authentication.
53+
func GetDependencyDiffResults(
54+
ctx context.Context, ownerName, repoName, baseSHA, headSHA string, scorecardChecksNames []string,
55+
changeTypesToCheck map[pkg.ChangeType]bool) ([]pkg.DependencyCheckResult, error) {
56+
// Fetch the raw dependency diffs.
57+
dCtx := dependencydiffContext{
58+
logger: log.NewLogger(log.InfoLevel),
59+
ownerName: ownerName,
60+
repoName: repoName,
61+
baseSHA: baseSHA,
62+
headSHA: headSHA,
63+
ctx: ctx,
64+
changeTypesToCheck: changeTypesToCheck,
65+
checkNamesToRun: scorecardChecksNames,
66+
}
67+
err := fetchRawDependencyDiffData(&dCtx)
68+
if err != nil {
69+
return nil, fmt.Errorf("error in fetchRawDependencyDiffData: %w", err)
70+
}
71+
72+
// Initialize the repo and client(s) corresponding to the checks to run.
73+
err = initRepoAndClientByChecks(&dCtx)
74+
if err != nil {
75+
return nil, fmt.Errorf("error in initRepoAndClientByChecks: %w", err)
76+
}
77+
err = getScorecardCheckResults(&dCtx)
78+
if err != nil {
79+
return nil, fmt.Errorf("error getting scorecard check results: %w", err)
80+
}
81+
return dCtx.results, nil
82+
}
83+
84+
func initRepoAndClientByChecks(dCtx *dependencydiffContext) error {
85+
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient, err := checker.GetClients(
86+
dCtx.ctx, path.Join(dCtx.ownerName, dCtx.repoName), "", dCtx.logger,
87+
)
88+
if err != nil {
89+
return fmt.Errorf("error creating the github repo: %w", err)
90+
}
91+
// If the caller doesn't specify the checks to run, run all checks and return all the clients.
92+
if dCtx.checkNamesToRun == nil || len(dCtx.checkNamesToRun) == 0 {
93+
dCtx.ghRepo, dCtx.ghRepoClient, dCtx.ossFuzzClient, dCtx.ciiClient, dCtx.vulnsClient =
94+
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient
95+
return nil
96+
}
97+
dCtx.ghRepo = repo
98+
dCtx.ghRepoClient = githubrepo.CreateGithubRepoClient(dCtx.ctx, dCtx.logger)
99+
for _, cn := range dCtx.checkNamesToRun {
100+
switch cn {
101+
case checks.CheckFuzzing:
102+
dCtx.ossFuzzClient = ossFuzzClient
103+
case checks.CheckCIIBestPractices:
104+
dCtx.ciiClient = ciiClient
105+
case checks.CheckVulnerabilities:
106+
dCtx.vulnsClient = vulnsClient
107+
}
108+
}
109+
return nil
110+
}
111+
112+
func getScorecardCheckResults(dCtx *dependencydiffContext) error {
113+
// Initialize the checks to run from the caller's input.
114+
checksToRun, err := policy.GetEnabled(nil, dCtx.checkNamesToRun, nil)
115+
if err != nil {
116+
return fmt.Errorf("error init scorecard checks: %w", err)
117+
}
118+
for _, d := range dCtx.dependencydiffs {
119+
depCheckResult := pkg.DependencyCheckResult{
120+
PackageURL: d.PackageURL,
121+
SourceRepository: d.SourceRepository,
122+
ChangeType: d.ChangeType,
123+
ManifestPath: d.ManifestPath,
124+
Ecosystem: d.Ecosystem,
125+
Version: d.Version,
126+
Name: d.Name,
127+
}
128+
// For now we skip those without source repo urls.
129+
// TODO (#2063): use the BigQuery dataset to supplement null source repo URLs to fetch the Scorecard results for them.
130+
if d.SourceRepository != nil && *d.SourceRepository != "" {
131+
if d.ChangeType != nil && (dCtx.changeTypesToCheck[*d.ChangeType] || dCtx.changeTypesToCheck == nil) {
132+
// Run scorecard on those types of dependencies that the caller would like to check.
133+
// If the input map changeTypesToCheck is empty, by default, we run checks for all valid types.
134+
// TODO (#2064): use the Scorecare REST API to retrieve the Scorecard result statelessly.
135+
scorecardResult, err := pkg.RunScorecards(
136+
dCtx.ctx,
137+
dCtx.ghRepo,
138+
// TODO (#2065): In future versions, ideally, this should be
139+
// the commitSHA corresponding to d.Version instead of HEAD.
140+
clients.HeadSHA,
141+
checksToRun,
142+
dCtx.ghRepoClient,
143+
dCtx.ossFuzzClient,
144+
dCtx.ciiClient,
145+
dCtx.vulnsClient,
146+
)
147+
// If the run fails, we leave the current dependency scorecard result empty and record the error
148+
// rather than letting the entire API return nil since we still expect results for other dependencies.
149+
if err != nil {
150+
depCheckResult.ScorecardResultsWithError.Error = sce.WithMessage(sce.ErrScorecardInternal,
151+
fmt.Sprintf("error running the scorecard checks: %v", err))
152+
} else { // Otherwise, we record the scorecard check results for this dependency.
153+
depCheckResult.ScorecardResultsWithError.ScorecardResults = &scorecardResult
154+
}
155+
}
156+
}
157+
dCtx.results = append(dCtx.results, depCheckResult)
158+
}
159+
return nil
160+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright 2022 Security Scorecard Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dependencydiff
16+
17+
import (
18+
"context"
19+
"path"
20+
"testing"
21+
22+
"github.com/ossf/scorecard/v4/clients"
23+
"github.com/ossf/scorecard/v4/log"
24+
)
25+
26+
// Test_fetchRawDependencyDiffData is a test function for fetchRawDependencyDiffData.
27+
func Test_fetchRawDependencyDiffData(t *testing.T) {
28+
t.Parallel()
29+
//nolint
30+
tests := []struct {
31+
name string
32+
dCtx dependencydiffContext
33+
wantEmpty bool
34+
wantErr bool
35+
}{
36+
{
37+
name: "error response",
38+
dCtx: dependencydiffContext{
39+
logger: log.NewLogger(log.InfoLevel),
40+
ctx: context.Background(),
41+
ownerName: "no_such_owner",
42+
repoName: "repo_not_exist",
43+
baseSHA: "base",
44+
headSHA: clients.HeadSHA,
45+
},
46+
wantEmpty: true,
47+
wantErr: true,
48+
},
49+
// Considering of the token usage, normal responses are tested in the e2e test.
50+
}
51+
for _, tt := range tests {
52+
tt := tt
53+
t.Run(tt.name, func(t *testing.T) {
54+
t.Parallel()
55+
err := fetchRawDependencyDiffData(&tt.dCtx)
56+
if (err != nil) != tt.wantErr {
57+
t.Errorf("fetchRawDependencyDiffData() error = {%v}, want error: %v", err, tt.wantErr)
58+
return
59+
}
60+
lenResults := len(tt.dCtx.dependencydiffs)
61+
if (lenResults == 0) != tt.wantEmpty {
62+
t.Errorf("want empty results: %v, got len of results:%d", tt.wantEmpty, lenResults)
63+
return
64+
}
65+
66+
})
67+
}
68+
}
69+
70+
func Test_initRepoAndClientByChecks(t *testing.T) {
71+
t.Parallel()
72+
//nolint
73+
tests := []struct {
74+
name string
75+
dCtx dependencydiffContext
76+
wantGhRepo, wantRepoClient, wantFuzzClient bool
77+
wantVulnClient, wantCIIClient bool
78+
wantErr bool
79+
}{
80+
{
81+
name: "error creating repo",
82+
dCtx: dependencydiffContext{
83+
logger: log.NewLogger(log.InfoLevel),
84+
ctx: context.Background(),
85+
ownerName: path.Join("host_not_exist.com", "owner_not_exist"),
86+
repoName: "repo_not_exist",
87+
checkNamesToRun: []string{},
88+
},
89+
wantGhRepo: false,
90+
wantRepoClient: false,
91+
wantFuzzClient: false,
92+
wantVulnClient: false,
93+
wantCIIClient: false,
94+
wantErr: true,
95+
},
96+
// Same as the above, putting the normal responses to the e2e test.
97+
}
98+
for _, tt := range tests {
99+
tt := tt
100+
t.Run(tt.name, func(t *testing.T) {
101+
t.Parallel()
102+
err := initRepoAndClientByChecks(&tt.dCtx)
103+
if (err != nil) != tt.wantErr {
104+
t.Errorf("initRepoAndClientByChecks() error = {%v}, want error: %v", err, tt.wantErr)
105+
return
106+
}
107+
if (tt.dCtx.ghRepo != nil) != tt.wantGhRepo {
108+
t.Errorf("init repo error, wantGhRepo: %v, got %v", tt.wantGhRepo, tt.dCtx.ghRepo)
109+
return
110+
}
111+
if (tt.dCtx.ghRepoClient != nil) != tt.wantRepoClient {
112+
t.Errorf("init repo error, wantRepoClient: %v, got %v", tt.wantRepoClient, tt.dCtx.ghRepoClient)
113+
return
114+
}
115+
if (tt.dCtx.ossFuzzClient != nil) != tt.wantFuzzClient {
116+
t.Errorf("init repo error, wantFuzzClient: %v, got %v", tt.wantFuzzClient, tt.dCtx.ossFuzzClient)
117+
return
118+
}
119+
if (tt.dCtx.vulnsClient != nil) != tt.wantVulnClient {
120+
t.Errorf("init repo error, wantVulnClient: %v, got %v", tt.wantVulnClient, tt.dCtx.vulnsClient)
121+
return
122+
}
123+
if (tt.dCtx.ciiClient != nil) != tt.wantCIIClient {
124+
t.Errorf("init repo error, wantCIIClient: %v, got %v", tt.wantCIIClient, tt.dCtx.ciiClient)
125+
return
126+
}
127+
})
128+
}
129+
}
130+
131+
func Test_getScorecardCheckResults(t *testing.T) {
132+
t.Parallel()
133+
//nolint
134+
tests := []struct {
135+
name string
136+
dCtx dependencydiffContext
137+
wantErr bool
138+
}{
139+
{
140+
name: "empty response",
141+
dCtx: dependencydiffContext{
142+
ctx: context.Background(),
143+
logger: log.NewLogger(log.InfoLevel),
144+
ownerName: "owner_not_exist",
145+
repoName: "repo_not_exist",
146+
},
147+
wantErr: false,
148+
},
149+
}
150+
for _, tt := range tests {
151+
tt := tt
152+
t.Run(tt.name, func(t *testing.T) {
153+
t.Parallel()
154+
err := initRepoAndClientByChecks(&tt.dCtx)
155+
if err != nil {
156+
t.Errorf("init repo and client error")
157+
return
158+
}
159+
err = getScorecardCheckResults(&tt.dCtx)
160+
if (err != nil) != tt.wantErr {
161+
t.Errorf("getScorecardCheckResults() error = {%v}, want error: %v", err, tt.wantErr)
162+
return
163+
}
164+
})
165+
}
166+
}

0 commit comments

Comments
 (0)