Skip to content

Commit

Permalink
add nuget package manager
Browse files Browse the repository at this point in the history
Signed-off-by: Avishay <avishay.balter@gmail.com>
  • Loading branch information
balteravishay committed May 16, 2023
1 parent 7736f36 commit 4cfcfe7
Show file tree
Hide file tree
Showing 9 changed files with 683 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ RESULTS

##### Using a Package manager

For projects in the `--npm`, `--pypi`, or `--rubygems` ecosystems, you have the
For projects in the `--npm`, `--pypi`, `--rubygems`, or `--nuget` ecosystems, you have the
option to run Scorecard using a package manager. Provide the package name to
run the checks on the corresponding GitHub source code.

Expand Down
97 changes: 96 additions & 1 deletion cmd/package_managers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ package cmd

import (
"encoding/json"
"encoding/xml"
"fmt"

"golang.org/x/exp/slices"

sce "github.com/ossf/scorecard/v4/errors"
)

Expand All @@ -27,7 +30,7 @@ type packageMangerResponse struct {
exists bool
}

func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems string,
func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems, nuget string,
manager packageManagerClient,
) (packageMangerResponse, error) {
if npm != "" {
Expand All @@ -51,6 +54,13 @@ func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems string,
associatedRepo: gitRepo,
}, err
}
if nuget != "" {
gitRepo, err := fetchGitRepositoryFromNuget(nuget, manager)
return packageMangerResponse{
exists: true,
associatedRepo: gitRepo,
}, err
}

return packageMangerResponse{}, nil
}
Expand All @@ -77,6 +87,34 @@ type rubyGemsSearchResults struct {
SourceCodeURI string `json:"source_code_uri"`
}

type nugetIndexResult struct {
ID string `json:"@id"`
Type string `json:"@type"`
}

type nugetIndexResults struct {
Resources []nugetIndexResult `json:"resources"`
}

type nugetpackageIndexResults struct {
Versions []string `json:"versions"`
}

type nugetNuspec struct {
XMLName xml.Name `xml:"package"`
Metadata nuspecMetadata `xml:"metadata"`
}

type nuspecMetadata struct {
XMLName xml.Name `xml:"metadata"`
Repository nuspecRepository `xml:"repository"`
}

type nuspecRepository struct {
XMLName xml.Name `xml:"repository"`
URL string `xml:"url,attr"`
}

// Gets the GitHub repository URL for the npm package.
func fetchGitRepositoryFromNPM(packageName string, packageManager packageManagerClient) (string, error) {
npmSearchURL := "https://registry.npmjs.org/-/v1/search?text=%s&size=1"
Expand Down Expand Up @@ -138,3 +176,60 @@ func fetchGitRepositoryFromRubyGems(packageName string, manager packageManagerCl
}
return v.SourceCodeURI, nil
}

// Gets the GitHub repository URL for the nuget package.
func fetchGitRepositoryFromNuget(packageName string, manager packageManagerClient) (string, error) {
nugetIndexURL := "https://api.nuget.org/v3/index.json"
respIndex, err := manager.GetURI(nugetIndexURL)
if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget index json: %v", err))
}
defer respIndex.Body.Close()
nugetIndexResults := &nugetIndexResults{}
err = json.NewDecoder(respIndex.Body).Decode(nugetIndexResults)

if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget index json: %v", err))
}
packageBaseAddressIndex := slices.IndexFunc(nugetIndexResults.Resources,
func(n nugetIndexResult) bool { return n.Type == "PackageBaseAddress/3.0.0" })
if packageBaseAddressIndex == -1 {
return "", sce.WithMessage(sce.ErrScorecardInternal, "failed to find package base URI at nuget index json")
}

nugetPackageBaseURL := nugetIndexResults.Resources[packageBaseAddressIndex].ID

respPackageIndex, err := manager.Get(nugetPackageBaseURL+"%s/index.json", packageName)
if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget package index json: %v", err))
}
defer respPackageIndex.Body.Close()
packageIndexResults := &nugetpackageIndexResults{}
err = json.NewDecoder(respPackageIndex.Body).Decode(packageIndexResults)

if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget package index json: %v", err))
}

lastVersion := packageIndexResults.Versions[len(packageIndexResults.Versions)-1]

respPackageSpec, err := manager.Get(nugetPackageBaseURL+"%[1]v/"+lastVersion+"/%[1]v.nuspec", packageName)
if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget package spec json: %v", err))
}
defer respPackageSpec.Body.Close()

packageSpecResults := &nugetNuspec{}
err = xml.NewDecoder(respPackageSpec.Body).Decode(packageSpecResults)

if err != nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget nuspec xml: %v", err))
}
if packageSpecResults.Metadata == (nuspecMetadata{}) ||
packageSpecResults.Metadata.Repository == (nuspecRepository{}) ||
packageSpecResults.Metadata.Repository.URL == "" {
return "", sce.WithMessage(sce.ErrScorecardInternal,
fmt.Sprintf("source repo is not defined for nuget package %v", packageName))
}
return packageSpecResults.Metadata.Repository.URL, nil
}
Loading

0 comments on commit 4cfcfe7

Please sign in to comment.