Skip to content

Commit

Permalink
Merge branch 'main' into feature-codeowners-branch-protection
Browse files Browse the repository at this point in the history
  • Loading branch information
raghavkaul committed Jul 18, 2022
2 parents 9610e62 + 10681da commit f98c399
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 10 deletions.
160 changes: 160 additions & 0 deletions dependencydiff/dependencydiff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2022 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dependencydiff

import (
"context"
"fmt"
"path"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks"
"github.com/ossf/scorecard/v4/clients"
"github.com/ossf/scorecard/v4/clients/githubrepo"
sce "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/log"
"github.com/ossf/scorecard/v4/pkg"
"github.com/ossf/scorecard/v4/policy"
)

// Depdiff is the exported name for dependency-diff.
const Depdiff = "Dependency-diff"

type dependencydiffContext struct {
logger *log.Logger
ownerName, repoName, baseSHA, headSHA string
ctx context.Context
ghRepo clients.Repo
ghRepoClient clients.RepoClient
ossFuzzClient clients.RepoClient
vulnsClient clients.VulnerabilitiesClient
ciiClient clients.CIIBestPracticesClient
changeTypesToCheck map[pkg.ChangeType]bool
checkNamesToRun []string
dependencydiffs []dependency
results []pkg.DependencyCheckResult
}

// GetDependencyDiffResults gets dependency changes between two given code commits BASE and HEAD
// along with the Scorecard check results of the dependencies, and returns a slice of DependencyCheckResult.
// TO use this API, an access token must be set following https://github.com/ossf/scorecard#authentication.
func GetDependencyDiffResults(
ctx context.Context, ownerName, repoName, baseSHA, headSHA string, scorecardChecksNames []string,
changeTypesToCheck map[pkg.ChangeType]bool) ([]pkg.DependencyCheckResult, error) {
// Fetch the raw dependency diffs.
dCtx := dependencydiffContext{
logger: log.NewLogger(log.InfoLevel),
ownerName: ownerName,
repoName: repoName,
baseSHA: baseSHA,
headSHA: headSHA,
ctx: ctx,
changeTypesToCheck: changeTypesToCheck,
checkNamesToRun: scorecardChecksNames,
}
err := fetchRawDependencyDiffData(&dCtx)
if err != nil {
return nil, fmt.Errorf("error in fetchRawDependencyDiffData: %w", err)
}

// Initialize the repo and client(s) corresponding to the checks to run.
err = initRepoAndClientByChecks(&dCtx)
if err != nil {
return nil, fmt.Errorf("error in initRepoAndClientByChecks: %w", err)
}
err = getScorecardCheckResults(&dCtx)
if err != nil {
return nil, fmt.Errorf("error getting scorecard check results: %w", err)
}
return dCtx.results, nil
}

func initRepoAndClientByChecks(dCtx *dependencydiffContext) error {
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient, err := checker.GetClients(
dCtx.ctx, path.Join(dCtx.ownerName, dCtx.repoName), "", dCtx.logger,
)
if err != nil {
return fmt.Errorf("error creating the github repo: %w", err)
}
// If the caller doesn't specify the checks to run, run all checks and return all the clients.
if dCtx.checkNamesToRun == nil || len(dCtx.checkNamesToRun) == 0 {
dCtx.ghRepo, dCtx.ghRepoClient, dCtx.ossFuzzClient, dCtx.ciiClient, dCtx.vulnsClient =
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient
return nil
}
dCtx.ghRepo = repo
dCtx.ghRepoClient = githubrepo.CreateGithubRepoClient(dCtx.ctx, dCtx.logger)
for _, cn := range dCtx.checkNamesToRun {
switch cn {
case checks.CheckFuzzing:
dCtx.ossFuzzClient = ossFuzzClient
case checks.CheckCIIBestPractices:
dCtx.ciiClient = ciiClient
case checks.CheckVulnerabilities:
dCtx.vulnsClient = vulnsClient
}
}
return nil
}

func getScorecardCheckResults(dCtx *dependencydiffContext) error {
// Initialize the checks to run from the caller's input.
checksToRun, err := policy.GetEnabled(nil, dCtx.checkNamesToRun, nil)
if err != nil {
return fmt.Errorf("error init scorecard checks: %w", err)
}
for _, d := range dCtx.dependencydiffs {
depCheckResult := pkg.DependencyCheckResult{
PackageURL: d.PackageURL,
SourceRepository: d.SourceRepository,
ChangeType: d.ChangeType,
ManifestPath: d.ManifestPath,
Ecosystem: d.Ecosystem,
Version: d.Version,
Name: d.Name,
}
// For now we skip those without source repo urls.
// TODO (#2063): use the BigQuery dataset to supplement null source repo URLs to fetch the Scorecard results for them.
if d.SourceRepository != nil && *d.SourceRepository != "" {
if d.ChangeType != nil && (dCtx.changeTypesToCheck[*d.ChangeType] || dCtx.changeTypesToCheck == nil) {
// Run scorecard on those types of dependencies that the caller would like to check.
// If the input map changeTypesToCheck is empty, by default, we run checks for all valid types.
// TODO (#2064): use the Scorecare REST API to retrieve the Scorecard result statelessly.
scorecardResult, err := pkg.RunScorecards(
dCtx.ctx,
dCtx.ghRepo,
// TODO (#2065): In future versions, ideally, this should be
// the commitSHA corresponding to d.Version instead of HEAD.
clients.HeadSHA,
checksToRun,
dCtx.ghRepoClient,
dCtx.ossFuzzClient,
dCtx.ciiClient,
dCtx.vulnsClient,
)
// If the run fails, we leave the current dependency scorecard result empty and record the error
// rather than letting the entire API return nil since we still expect results for other dependencies.
if err != nil {
depCheckResult.ScorecardResultsWithError.Error = sce.WithMessage(sce.ErrScorecardInternal,
fmt.Sprintf("error running the scorecard checks: %v", err))
} else { // Otherwise, we record the scorecard check results for this dependency.
depCheckResult.ScorecardResultsWithError.ScorecardResults = &scorecardResult
}
}
}
dCtx.results = append(dCtx.results, depCheckResult)
}
return nil
}
166 changes: 166 additions & 0 deletions dependencydiff/dependencydiff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright 2022 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dependencydiff

import (
"context"
"path"
"testing"

"github.com/ossf/scorecard/v4/clients"
"github.com/ossf/scorecard/v4/log"
)

// Test_fetchRawDependencyDiffData is a test function for fetchRawDependencyDiffData.
func Test_fetchRawDependencyDiffData(t *testing.T) {
t.Parallel()
//nolint
tests := []struct {
name string
dCtx dependencydiffContext
wantEmpty bool
wantErr bool
}{
{
name: "error response",
dCtx: dependencydiffContext{
logger: log.NewLogger(log.InfoLevel),
ctx: context.Background(),
ownerName: "no_such_owner",
repoName: "repo_not_exist",
baseSHA: "base",
headSHA: clients.HeadSHA,
},
wantEmpty: true,
wantErr: true,
},
// Considering of the token usage, normal responses are tested in the e2e test.
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := fetchRawDependencyDiffData(&tt.dCtx)
if (err != nil) != tt.wantErr {
t.Errorf("fetchRawDependencyDiffData() error = {%v}, want error: %v", err, tt.wantErr)
return
}
lenResults := len(tt.dCtx.dependencydiffs)
if (lenResults == 0) != tt.wantEmpty {
t.Errorf("want empty results: %v, got len of results:%d", tt.wantEmpty, lenResults)
return
}

})
}
}

func Test_initRepoAndClientByChecks(t *testing.T) {
t.Parallel()
//nolint
tests := []struct {
name string
dCtx dependencydiffContext
wantGhRepo, wantRepoClient, wantFuzzClient bool
wantVulnClient, wantCIIClient bool
wantErr bool
}{
{
name: "error creating repo",
dCtx: dependencydiffContext{
logger: log.NewLogger(log.InfoLevel),
ctx: context.Background(),
ownerName: path.Join("host_not_exist.com", "owner_not_exist"),
repoName: "repo_not_exist",
checkNamesToRun: []string{},
},
wantGhRepo: false,
wantRepoClient: false,
wantFuzzClient: false,
wantVulnClient: false,
wantCIIClient: false,
wantErr: true,
},
// Same as the above, putting the normal responses to the e2e test.
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := initRepoAndClientByChecks(&tt.dCtx)
if (err != nil) != tt.wantErr {
t.Errorf("initRepoAndClientByChecks() error = {%v}, want error: %v", err, tt.wantErr)
return
}
if (tt.dCtx.ghRepo != nil) != tt.wantGhRepo {
t.Errorf("init repo error, wantGhRepo: %v, got %v", tt.wantGhRepo, tt.dCtx.ghRepo)
return
}
if (tt.dCtx.ghRepoClient != nil) != tt.wantRepoClient {
t.Errorf("init repo error, wantRepoClient: %v, got %v", tt.wantRepoClient, tt.dCtx.ghRepoClient)
return
}
if (tt.dCtx.ossFuzzClient != nil) != tt.wantFuzzClient {
t.Errorf("init repo error, wantFuzzClient: %v, got %v", tt.wantFuzzClient, tt.dCtx.ossFuzzClient)
return
}
if (tt.dCtx.vulnsClient != nil) != tt.wantVulnClient {
t.Errorf("init repo error, wantVulnClient: %v, got %v", tt.wantVulnClient, tt.dCtx.vulnsClient)
return
}
if (tt.dCtx.ciiClient != nil) != tt.wantCIIClient {
t.Errorf("init repo error, wantCIIClient: %v, got %v", tt.wantCIIClient, tt.dCtx.ciiClient)
return
}
})
}
}

func Test_getScorecardCheckResults(t *testing.T) {
t.Parallel()
//nolint
tests := []struct {
name string
dCtx dependencydiffContext
wantErr bool
}{
{
name: "empty response",
dCtx: dependencydiffContext{
ctx: context.Background(),
logger: log.NewLogger(log.InfoLevel),
ownerName: "owner_not_exist",
repoName: "repo_not_exist",
},
wantErr: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := initRepoAndClientByChecks(&tt.dCtx)
if err != nil {
t.Errorf("init repo and client error")
return
}
err = getScorecardCheckResults(&tt.dCtx)
if (err != nil) != tt.wantErr {
t.Errorf("getScorecardCheckResults() error = {%v}, want error: %v", err, tt.wantErr)
return
}
})
}
}
Loading

0 comments on commit f98c399

Please sign in to comment.