diff --git a/scanpullrequest/scanallpullrequests_test.go b/scanpullrequest/scanallpullrequests_test.go index aff9a184d..c851fc72c 100644 --- a/scanpullrequest/scanallpullrequests_test.go +++ b/scanpullrequest/scanallpullrequests_test.go @@ -214,6 +214,8 @@ func getMockClient(t *testing.T, frogbotMessages *[]string, mockParams ...MockPa client.EXPECT().DeletePullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() // Return private repositories visibility client.EXPECT().GetRepositoryInfo(context.Background(), gomock.Any(), gomock.Any()).Return(vcsclient.RepositoryInfo{RepositoryVisibility: vcsclient.Private}, nil).AnyTimes() + // Return latest commit info for XSC context. + client.EXPECT().GetLatestCommit(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return(vcsclient.CommitInfo{}, nil).AnyTimes() } return client } diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 0e43c42a9..8537e9a97 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -31,8 +31,8 @@ type ScanRepositoryCmd struct { dryRun bool // When dryRun is enabled, dryRunRepoPath specifies the repository local path to clone dryRunRepoPath string - // The details of the current scan - details *utils.ScanDetails + // The scanDetails of the current scan + scanDetails *utils.ScanDetails // The base working directory baseWd string // The git client the command performs git operations with @@ -58,6 +58,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixRepository(repository *utils.Repository, if err = cfp.setCommandPrerequisites(repository, branch, client); err != nil { return } + cfp.scanDetails.SetXscGitInfoContext(branch, repository.Project, client) if err = cfp.scanAndFixBranch(repository); err != nil { return } @@ -79,7 +80,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er err = errors.Join(err, restoreBaseDir(), fileutils.RemoveTempDir(clonedRepoDir)) }() for i := range repository.Projects { - cfp.details.Project = &repository.Projects[i] + cfp.scanDetails.Project = &repository.Projects[i] cfp.projectTech = "" if err = cfp.scanAndFixProject(repository); err != nil { return @@ -89,37 +90,39 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er } func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, branch string, client vcsclient.VcsClient) (err error) { - cfp.details = utils.NewScanDetails(client, &repository.Server, &repository.Git). + + cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). SetXrayGraphScanParams(repository.Watches, repository.JFrogProjectKey). SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). SetBaseBranch(branch). SetFixableOnly(repository.FixableOnly). SetMinSeverity(repository.MinSeverity) + cfp.aggregateFixes = repository.Git.AggregateFixes cfp.OutputWriter = outputwriter.GetCompatibleOutputWriter(repository.GitProvider) - repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName) + repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) if err != nil { return } - remoteHttpsGitUrl := repositoryInfo.CloneInfo.HTTP + cfp.scanDetails.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP cfp.gitManager, err = utils.NewGitManager(). - SetAuth(cfp.details.Username, cfp.details.Token). + SetAuth(cfp.scanDetails.Username, cfp.scanDetails.Token). SetDryRun(cfp.dryRun, cfp.dryRunRepoPath). - SetRemoteGitUrl(remoteHttpsGitUrl) + SetRemoteGitUrl(cfp.scanDetails.Git.RepositoryCloneUrl) if err != nil { return } - _, err = cfp.gitManager.SetGitParams(cfp.details.Git) + _, err = cfp.gitManager.SetGitParams(cfp.scanDetails.Git) return } func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) error { var fixNeeded bool // A map that contains the full project paths as a keys - // The value is a map of vulnerable package names -> the details of the vulnerable packages. - // That means we have a map of all the vulnerabilities that were found in a specific folder, along with their full details. + // The value is a map of vulnerable package names -> the scanDetails of the vulnerable packages. + // That means we have a map of all the vulnerabilities that were found in a specific folder, along with their full scanDetails. vulnerabilitiesByPathMap := make(map[string]map[string]*utils.VulnerabilityDetails) - projectFullPathWorkingDirs := utils.GetFullPathWorkingDirs(cfp.details.Project.WorkingDirs, cfp.baseWd) + projectFullPathWorkingDirs := utils.GetFullPathWorkingDirs(cfp.scanDetails.Project.WorkingDirs, cfp.baseWd) for _, fullPathWd := range projectFullPathWorkingDirs { scanResults, err := cfp.scan(fullPathWd) if err != nil { @@ -129,7 +132,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) er if repository.GitProvider.String() == vcsutils.GitHub.String() { // Uploads Sarif results to GitHub in order to view the scan in the code scanning UI // Currently available on GitHub only - if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.details.BaseBranch(), cfp.details.Client()); err != nil { + if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client()); err != nil { log.Warn(err) } } @@ -153,7 +156,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) er // Audit the dependencies of the current commit. func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*audit.Results, error) { // Audit commit code - auditResults, err := cfp.details.RunInstallAndAudit(currentWorkingDir) + auditResults, err := cfp.scanDetails.RunInstallAndAudit(currentWorkingDir) if err != nil { return nil, err } @@ -172,7 +175,7 @@ func (cfp *ScanRepositoryCmd) getVulnerabilitiesMap(scanResults *xrayutils.Exten // Nothing to fix, return if len(vulnerabilitiesMap) == 0 { - log.Info("Didn't find vulnerable dependencies with existing fix versions for", cfp.details.RepoName) + log.Info("Didn't find vulnerable dependencies with existing fix versions for", cfp.scanDetails.RepoName) } return vulnerabilitiesMap, nil } @@ -216,7 +219,7 @@ func (cfp *ScanRepositoryCmd) fixProjectVulnerabilities(fullProjectPath string, } // After fixing the current vulnerability, checkout to the base branch to start fixing the next vulnerability - if e := cfp.gitManager.Checkout(cfp.details.BaseBranch()); e != nil { + if e := cfp.gitManager.Checkout(cfp.scanDetails.BaseBranch()); e != nil { err = errors.Join(err, cfp.handleUpdatePackageErrors(e)) return } @@ -284,7 +287,7 @@ func (cfp *ScanRepositoryCmd) handleUpdatePackageErrors(err error) error { func (cfp *ScanRepositoryCmd) fixSinglePackageAndCreatePR(vulnDetails *utils.VulnerabilityDetails) (err error) { fixVersion := vulnDetails.SuggestedFixedVersion log.Debug("Attempting to fix", vulnDetails.ImpactedDependencyName, "with", fixVersion) - fixBranchName, err := cfp.gitManager.GenerateFixBranchName(cfp.details.BaseBranch(), vulnDetails.ImpactedDependencyName, fixVersion) + fixBranchName, err := cfp.gitManager.GenerateFixBranchName(cfp.scanDetails.BaseBranch(), vulnDetails.ImpactedDependencyName, fixVersion) if err != nil { return } @@ -330,8 +333,8 @@ func (cfp *ScanRepositoryCmd) openFixingPullRequest(fixBranchName string, vulnDe if err != nil { return } - log.Debug("Creating Pull Request form:", fixBranchName, " to:", cfp.details.BaseBranch()) - return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.BaseBranch(), pullRequestTitle, prBody) + log.Debug("Creating Pull Request form:", fixBranchName, " to:", cfp.scanDetails.BaseBranch()) + return cfp.scanDetails.Client().CreatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, fixBranchName, cfp.scanDetails.BaseBranch(), pullRequestTitle, prBody) } // openAggregatedPullRequest handles the opening or updating of a pull request when the aggregate mode is active. @@ -349,11 +352,11 @@ func (cfp *ScanRepositoryCmd) openAggregatedPullRequest(fixBranchName string, pu return } if pullRequestInfo == nil { - log.Info("Creating Pull Request from:", fixBranchName, "to:", cfp.details.BaseBranch()) - return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.BaseBranch(), pullRequestTitle, prBody) + log.Info("Creating Pull Request from:", fixBranchName, "to:", cfp.scanDetails.BaseBranch()) + return cfp.scanDetails.Client().CreatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, fixBranchName, cfp.scanDetails.BaseBranch(), pullRequestTitle, prBody) } - log.Info("Updating Pull Request from:", fixBranchName, "to:", cfp.details.BaseBranch()) - return cfp.details.Client().UpdatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, pullRequestTitle, prBody, "", int(pullRequestInfo.ID), vcsutils.Open) + log.Info("Updating Pull Request from:", fixBranchName, "to:", cfp.scanDetails.BaseBranch()) + return cfp.scanDetails.Client().UpdatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, pullRequestTitle, prBody, "", int(pullRequestInfo.ID), vcsutils.Open) } func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails ...*utils.VulnerabilityDetails) (string, string, error) { @@ -378,7 +381,7 @@ func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails . func (cfp *ScanRepositoryCmd) cloneRepositoryAndCheckoutToBranch() (tempWd string, restoreDir func() error, err error) { if cfp.dryRun { - tempWd = filepath.Join(cfp.dryRunRepoPath, cfp.details.RepoName) + tempWd = filepath.Join(cfp.dryRunRepoPath, cfp.scanDetails.RepoName) } else { // Create temp working directory if tempWd, err = fileutils.CreateTempDir(); err != nil { @@ -388,7 +391,7 @@ func (cfp *ScanRepositoryCmd) cloneRepositoryAndCheckoutToBranch() (tempWd strin log.Debug("Created temp working directory:", tempWd) // Clone the content of the repo to the new working directory - if err = cfp.gitManager.Clone(tempWd, cfp.details.BaseBranch()); err != nil { + if err = cfp.gitManager.Clone(tempWd, cfp.scanDetails.BaseBranch()); err != nil { return } @@ -471,7 +474,7 @@ func (cfp *ScanRepositoryCmd) updatePackageToFixedVersion(vulnDetails *utils.Vul handler := cfp.handlers[vulnDetails.Technology] if handler == nil { - handler = packagehandlers.GetCompatiblePackageHandler(vulnDetails, cfp.details) + handler = packagehandlers.GetCompatiblePackageHandler(vulnDetails, cfp.scanDetails) cfp.handlers[vulnDetails.Technology] = handler } else if _, unsupported := handler.(*packagehandlers.UnsupportedPackageHandler); unsupported { return @@ -497,7 +500,7 @@ func (cfp *ScanRepositoryCmd) getRemoteBranchScanHash(prBody string) string { } func (cfp *ScanRepositoryCmd) getOpenPullRequestBySourceBranch(branchName string) (prInfo *vcsclient.PullRequestInfo, err error) { - list, err := cfp.details.Client().ListOpenPullRequestsWithBody(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName) + list, err := cfp.scanDetails.Client().ListOpenPullRequestsWithBody(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) if err != nil { return } @@ -542,7 +545,7 @@ func (cfp *ScanRepositoryCmd) aggregateFixAndOpenPullRequest(vulnerabilitiesMap } } log.Info("-----------------------------------------------------------------") - if e := cfp.gitManager.Checkout(cfp.details.BaseBranch()); e != nil { + if e := cfp.gitManager.Checkout(cfp.scanDetails.BaseBranch()); e != nil { err = errors.Join(err, e) } return diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index cd3739913..75562b82d 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -391,7 +391,7 @@ func TestPackageTypeFromScan(t *testing.T) { Project: &frogbotParams.Projects[0], ServerDetails: &frogbotParams.Server, } - testScan.details = &scanSetup + testScan.scanDetails = &scanSetup scanResponse, err := testScan.scan(tmpDir) assert.NoError(t, err) verifyTechnologyNaming(t, scanResponse.ExtendedScanResults.XrayResults, pkg.packageType) diff --git a/utils/params.go b/utils/params.go index 6440e76a3..3d5dfa6c8 100644 --- a/utils/params.go +++ b/utils/params.go @@ -232,6 +232,7 @@ type Git struct { EmailAuthor string `yaml:"emailAuthor,omitempty"` AggregateFixes bool `yaml:"aggregateFixes,omitempty"` PullRequestDetails vcsclient.PullRequestInfo + RepositoryCloneUrl string } func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git) (err error) { diff --git a/utils/scandetails.go b/utils/scandetails.go index 01dc28ab0..0026b8e26 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -1,6 +1,7 @@ package utils import ( + "context" "errors" "fmt" "github.com/jfrog/froggit-go/vcsclient" @@ -9,6 +10,7 @@ import ( xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" + "os/exec" "path/filepath" "strings" @@ -171,6 +173,44 @@ func (sc *ScanDetails) runInstallCommand() ([]byte, error) { return MapTechToResolvingFunc[sc.InstallCommandName](sc) } +func (sc *ScanDetails) SetXscGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) *ScanDetails { + XscGitInfoContext, err := sc.createGitInfoContext(scannedBranch, gitProject, client) + if err != nil { + log.Debug("failed trying to create GitInfoContext for Xsc with the following error: ", err.Error()) + return sc + } + sc.XscGitInfoContext = XscGitInfoContext + return sc +} + +// CreateGitInfoContext Creates GitInfoContext for XSC scans, this is optional. +// ScannedBranch - name of the branch we are scanning. +// GitProject - [Optional] relevant for azure repos and Bitbucket server. +// Client vscClient +func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) (gitInfo *services.XscGitInfoContext, err error) { + latestCommit, err := client.GetLatestCommit(context.Background(), sc.RepoOwner, sc.RepoName, scannedBranch) + if err != nil { + return nil, fmt.Errorf("failed getting latest commit, repository: %s, branch: %s. error: %s ", sc.RepoName, scannedBranch, err.Error()) + } + // In some VCS providers, there are no git projects, fallback to the repository owner. + if gitProject == "" { + gitProject = sc.RepoOwner + } + gitInfo = &services.XscGitInfoContext{ + // Use Clone URLs as Repo Url, on browsers it will redirect to repository URLS. + GitRepoUrl: sc.Git.RepositoryCloneUrl, + GitRepoName: sc.RepoName, + GitProvider: sc.GitProvider.String(), + GitProject: gitProject, + BranchName: scannedBranch, + LastCommit: latestCommit.Url, + CommitHash: latestCommit.Hash, + CommitMessage: latestCommit.Message, + CommitAuthor: latestCommit.AuthorName, + } + return +} + func GetFullPathWorkingDirs(workingDirs []string, baseWd string) []string { var fullPathWds []string if len(workingDirs) != 0 {