Skip to content

Commit edcc8cf

Browse files
committed
initial implementation of ossf#1369 (comment) to provide more license details
Signed-off-by: Scott Hissam <[email protected]>
1 parent c46a581 commit edcc8cf

File tree

9 files changed

+203
-10
lines changed

9 files changed

+203
-10
lines changed

Diff for: checker/raw_result.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,35 @@ type MaintainedData struct {
125125
ArchivedStatus ArchivedStatus
126126
}
127127

128+
type LicenseAttributionType string
129+
130+
const (
131+
// forms of security policy hints being evaluated.
132+
LicenseAttributionTypeOther LicenseAttributionType = "otherAttribution"
133+
LicenseAttributionTypeRepo LicenseAttributionType = "repoAttribution"
134+
LicenseAttributionTypeScorecard LicenseAttributionType = "scorecardAttribution"
135+
)
136+
137+
// license details
138+
type License struct {
139+
Key string // repo specified key
140+
Name string // OSI standardized license name
141+
Size int // size of the license file found (default: 0)
142+
SpdxId string // SPDX standardized identifier
143+
Attribution LicenseAttributionType // source of licensing information
144+
}
145+
146+
// one file contains one license
147+
type LicenseFile struct {
148+
File File
149+
License License
150+
}
151+
128152
// LicenseData contains the raw results
129153
// for the License check.
154+
// Some repos may have more than one license.
130155
type LicenseData struct {
131-
Files []File
156+
LicenseFiles []LicenseFile
132157
}
133158

134159
// CodeReviewData contains the raw results

Diff for: checks/evaluation/license.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ func License(name string, dl checker.DetailLogger,
2929
}
3030

3131
// Apply the policy evaluation.
32-
if r.Files == nil || len(r.Files) == 0 {
32+
if r.LicenseFiles == nil || len(r.LicenseFiles) == 0 {
3333
return checker.CreateMinScoreResult(name, "license file not detected")
3434
}
3535

36-
for _, f := range r.Files {
36+
for _, f := range r.LicenseFiles {
3737
dl.Info(&checker.LogMessage{
38-
Path: f.Path,
38+
Path: f.File.Path,
3939
Type: checker.FileTypeSource,
4040
Offset: 1,
4141
})

Diff for: checks/raw/license.go

+37-4
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,49 @@ func License(c *checker.CheckRequest) (checker.LicenseData, error) {
6969
var results checker.LicenseData
7070
var path string
7171

72+
licensesFound, lerr := c.RepoClient.ListLicenses()
73+
if lerr == nil && len(licensesFound) > 0 {
74+
//fmt.Printf ("'%T' of size '%d' has '%+v'\n", licensesFound, len(licensesFound), licensesFound)
75+
for _, v := range licensesFound {
76+
results.LicenseFiles = append(results.LicenseFiles,
77+
checker.LicenseFile{
78+
File: checker.File{
79+
Path: v.Path,
80+
Type: checker.FileTypeSource,
81+
},
82+
License: checker.License{
83+
Key: v.Key,
84+
Name: v.Name,
85+
Size: v.Size,
86+
SpdxId: v.SPDXId,
87+
Attribution: checker.LicenseAttributionTypeRepo,
88+
},
89+
})
90+
}
91+
return results, nil
92+
}
93+
94+
// no licenses reported by repo API, continue looking for files
7295
err := fileparser.OnAllFilesDo(c.RepoClient, isLicenseFile, &path)
7396
if err != nil {
7497
return results, fmt.Errorf("fileparser.OnAllFilesDo: %w", err)
7598
}
7699

100+
// scorecard search stops at first candidate (isLicenseFile) license file found
77101
if path != "" {
78-
results.Files = append(results.Files,
79-
checker.File{
80-
Path: path,
81-
Type: checker.FileTypeSource,
102+
results.LicenseFiles = append(results.LicenseFiles,
103+
checker.LicenseFile{
104+
File: checker.File{
105+
Path: path,
106+
Type: checker.FileTypeSource,
107+
},
108+
License: checker.License{
109+
Key: "",
110+
Name: "",
111+
Size: int(0),
112+
SpdxId: "",
113+
Attribution: checker.LicenseAttributionTypeScorecard,
114+
},
82115
})
83116
}
84117

Diff for: clients/githubrepo/client.go

+12
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Client struct {
5353
searchCommits *searchCommitsHandler
5454
webhook *webhookHandler
5555
languages *languagesHandler
56+
licenses *licensesHandler
5657
ctx context.Context
5758
tarball tarballHandler
5859
}
@@ -113,6 +114,9 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string) error {
113114

114115
// Setup languagesHandler.
115116
client.languages.init(client.ctx, client.repourl)
117+
118+
// Setup licensesHandler.
119+
client.licenses.init(client.ctx, client.repourl)
116120
return nil
117121
}
118122

@@ -213,6 +217,11 @@ func (client *Client) ListProgrammingLanguages() ([]clients.Language, error) {
213217
return client.languages.listProgrammingLanguages()
214218
}
215219

220+
// ListLicenses implements RepoClient.ListLicenses
221+
func (client *Client) ListLicenses() ([]clients.License, error) {
222+
return client.licenses.listLicenses()
223+
}
224+
216225
// Search implements RepoClient.Search.
217226
func (client *Client) Search(request clients.SearchRequest) (clients.SearchResponse, error) {
218227
return client.search.search(request)
@@ -273,6 +282,9 @@ func CreateGithubRepoClientWithTransport(ctx context.Context, rt http.RoundTripp
273282
languages: &languagesHandler{
274283
ghclient: client,
275284
},
285+
licenses: &licensesHandler{
286+
ghclient: client,
287+
},
276288
tarball: tarballHandler{
277289
httpClient: httpClient,
278290
},

Diff for: clients/githubrepo/licenses.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2021 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 githubrepo
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"path"
21+
"sync"
22+
23+
"github.com/google/go-github/v38/github"
24+
25+
"github.com/ossf/scorecard/v4/clients"
26+
)
27+
28+
type licensesHandler struct {
29+
ghclient *github.Client
30+
once *sync.Once
31+
ctx context.Context
32+
errSetup error
33+
repourl *repoURL
34+
licenses []clients.License
35+
}
36+
37+
func (handler *licensesHandler) init(ctx context.Context, repourl *repoURL) {
38+
handler.ctx = ctx
39+
handler.repourl = repourl
40+
handler.errSetup = nil
41+
handler.once = new(sync.Once)
42+
fmt.Println ("licenses handler initialized\n")
43+
}
44+
45+
// TODO: Can add support to parse the raw response JSON and mark licenses that are not in
46+
// our defined License consts in clients/licenses.go as "not supported licenses".
47+
func (handler *licensesHandler) setup() error {
48+
handler.once.Do(func() {
49+
client := handler.ghclient
50+
// defined at docs.github.com/en/rest/licenses#get-the-license-for-a-repository
51+
reqURL := path.Join("repos", handler.repourl.owner, handler.repourl.repo, "license")
52+
req, err := client.NewRequest("GET", reqURL, nil)
53+
if err != nil {
54+
handler.errSetup = fmt.Errorf("request for repo license failed with %w", err)
55+
return
56+
}
57+
bodyJSON := github.RepositoryLicense{}
58+
// The client.repoClient.Do API writes the response body to var bodyJSON,
59+
// so we can ignore the first returned variable (the entire http response object)
60+
// since we only need the response body here.
61+
_, err = client.Do(handler.ctx, req, &bodyJSON)
62+
if err != nil {
63+
handler.errSetup = fmt.Errorf("response for repo license failed with %w", err)
64+
return
65+
}
66+
67+
// TODO: github.RepositoryLicense{} only supports one license per repo
68+
// should that change to an array of licenses, the change would
69+
// be here to iterate over any such range.
70+
handler.licenses = append(handler.licenses, clients.License{
71+
Key: bodyJSON.GetLicense().GetKey(),
72+
Name: bodyJSON.GetLicense().GetName(),
73+
SPDXId: bodyJSON.GetLicense().GetSPDXID(),
74+
Path: bodyJSON.GetName(),
75+
Type: bodyJSON.GetType(),
76+
Size: bodyJSON.GetSize(),
77+
},
78+
)
79+
handler.errSetup = nil
80+
})
81+
82+
return handler.errSetup
83+
}
84+
85+
func (handler *licensesHandler) listLicenses() ([]clients.License, error) {
86+
if err := handler.setup(); err != nil {
87+
return nil, fmt.Errorf("error during licensesHandler.setup: %w", err)
88+
}
89+
return handler.licenses, nil
90+
}

Diff for: clients/licenses.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 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 clients
16+
17+
// License represents a customized struct for licenses used by clients.
18+
// from pkg.go.dev/github.com/google/go-github/github#RepositoryLicense
19+
type License struct {
20+
Key string // RepositoryLicense.GetLicense().GetKey()
21+
Name string // RepositoryLicense.GetLicense().GetName()
22+
Path string // RepositoryLicense.GetName()
23+
Size int // RepositoryLicense.GetSize()
24+
SPDXId string // RepositoryLicense.GetLicense().GetSPDXID()
25+
Type string // RepositoryLicense.GetType()
26+
}

Diff for: clients/localdir/client.go

+6
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ func (client *localDirClient) ListProgrammingLanguages() ([]clients.Language, er
235235
return nil, fmt.Errorf("ListProgrammingLanguages: %w", clients.ErrUnsupportedFeature)
236236
}
237237

238+
// ListLicenses implements RepoClient.ListLicenses.
239+
// TODO: add ListLicenses support for local directories.
240+
func (client *localDirClient) ListLicenses() ([]clients.License, error) {
241+
return nil, fmt.Errorf("ListLicenses: %w", clients.ErrUnsupportedFeature)
242+
}
243+
238244
func (client *localDirClient) GetCreatedAt() (time.Time, error) {
239245
return time.Time{}, fmt.Errorf("GetCreatedAt: %w", clients.ErrUnsupportedFeature)
240246
}

Diff for: clients/repo_client.go

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type RepoClient interface {
3939
GetDefaultBranch() (*BranchRef, error)
4040
ListCommits() ([]Commit, error)
4141
ListIssues() ([]Issue, error)
42+
ListLicenses() ([]License, error)
4243
ListReleases() ([]Release, error)
4344
ListContributors() ([]User, error)
4445
ListSuccessfulWorkflowRuns(filename string) ([]WorkflowRun, error)

Diff for: pkg/json_raw_results.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,11 @@ func (r *jsonScorecardRawResult) addCodeReviewRawResults(cr *checker.CodeReviewD
563563
//nolint:unparam
564564
func (r *jsonScorecardRawResult) addLicenseRawResults(ld *checker.LicenseData) error {
565565
r.Results.Licenses = []jsonLicense{}
566-
for _, file := range ld.Files {
566+
for _, file := range ld.LicenseFiles {
567567
r.Results.Licenses = append(r.Results.Licenses,
568568
jsonLicense{
569569
File: jsonFile{
570-
Path: file.Path,
570+
Path: file.File.Path,
571571
},
572572
},
573573
)

0 commit comments

Comments
 (0)