mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 08:55:27 +03:00
🌱 Add license probe (#3465)
* 🌱 Add license probe
Signed-off-by: AdamKorcz <adam@adalogics.com>
* [WIP] add two remaining license checks as probes
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix nits
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Use Errorf in test
Signed-off-by: AdamKorcz <adam@adalogics.com>
* use zrunner
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix wrong return value
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix linting issues and remove empty default
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix double if statement
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Remove struct field from test
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Add test for nil-case of license files slice
Signed-off-by: AdamKorcz <adam@adalogics.com>
* rewrite multiple def.ymls
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix nits
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Add unit test with multiple unapproved license files
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Add link to approved license formats
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix linting
Signed-off-by: AdamKorcz <adam@adalogics.com>
* remove comment
Signed-off-by: AdamKorcz <adam@adalogics.com>
* preserve logging from original check
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix typo
Signed-off-by: AdamKorcz <adam@adalogics.com>
* remove redundant map manipulation
Signed-off-by: AdamKorcz <adam@adalogics.com>
* rename hasApproveLicense probe
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Return OutcomeNotApplicable if hasFSFOrOSIApprovedLicense probe does not find a license
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Include license file locations in log
Signed-off-by: AdamKorcz <adam@adalogics.com>
* fix linting issues
Signed-off-by: AdamKorcz <adam@adalogics.com>
* replace strings filtering with OutcomeNotApplicable in hasLicenseFileAtTopDir probe
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Fix linter issue
Signed-off-by: AdamKorcz <adam@adalogics.com>
* Include location of found license files
Signed-off-by: AdamKorcz <adam@adalogics.com>
---------
Signed-off-by: AdamKorcz <adam@adalogics.com>
This commit is contained in:
parent
622f10442c
commit
0e3a5233ae
@ -18,78 +18,92 @@ import (
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/hasFSFOrOSIApprovedLicense"
|
||||
"github.com/ossf/scorecard/v4/probes/hasLicenseFile"
|
||||
"github.com/ossf/scorecard/v4/probes/hasLicenseFileAtTopDir"
|
||||
)
|
||||
|
||||
func scoreLicenseCriteria(f *checker.LicenseFile,
|
||||
dl checker.DetailLogger,
|
||||
) int {
|
||||
var score int
|
||||
msg := checker.LogMessage{
|
||||
Path: "",
|
||||
Type: finding.FileTypeNone,
|
||||
Text: "",
|
||||
Offset: 1,
|
||||
}
|
||||
msg.Path = f.File.Path
|
||||
msg.Type = finding.FileTypeSource
|
||||
// #1 a license file was found.
|
||||
score += 6
|
||||
|
||||
// #2 the licence was found at the top-level or LICENSE/ folder.
|
||||
switch f.LicenseInformation.Attribution {
|
||||
case checker.LicenseAttributionTypeAPI, checker.LicenseAttributionTypeHeuristics:
|
||||
// both repoAPI and scorecard (not using the API) follow checks.md
|
||||
// for a file to be found it must have been in the correct location
|
||||
// award location points.
|
||||
score += 3
|
||||
msg.Text = "License file found in expected location"
|
||||
dl.Info(&msg)
|
||||
// for repo attribution prepare warning if not an recognized license"
|
||||
msg.Text = "Any licence detected not an FSF or OSI recognized license"
|
||||
case checker.LicenseAttributionTypeOther:
|
||||
// TODO ascertain location found
|
||||
score += 0
|
||||
msg.Text = "License file found in unexpected location"
|
||||
dl.Warn(&msg)
|
||||
// for non repo attribution not the license detection is not supported
|
||||
msg.Text = "Detecting license content not supported"
|
||||
default:
|
||||
}
|
||||
|
||||
// #3 is the license either an FSF or OSI recognized/approved license
|
||||
if f.LicenseInformation.Approved {
|
||||
score += 1
|
||||
msg.Text = "FSF or OSI recognized license"
|
||||
dl.Info(&msg)
|
||||
} else {
|
||||
// message text for this condition set above
|
||||
dl.Warn(&msg)
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// License applies the score policy for the License check.
|
||||
func License(name string, dl checker.DetailLogger,
|
||||
r *checker.LicenseData,
|
||||
func License(name string,
|
||||
findings []finding.Finding,
|
||||
dl checker.DetailLogger,
|
||||
) checker.CheckResult {
|
||||
var score int
|
||||
if r == nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
|
||||
// We have 3 unique probes, each should have a finding.
|
||||
expectedProbes := []string{
|
||||
hasLicenseFile.Probe,
|
||||
hasFSFOrOSIApprovedLicense.Probe,
|
||||
hasLicenseFileAtTopDir.Probe,
|
||||
}
|
||||
|
||||
if !finding.UniqueProbesEqual(findings, expectedProbes) {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
// Apply the policy evaluation.
|
||||
if r.LicenseFiles == nil || len(r.LicenseFiles) == 0 {
|
||||
// Compute the score.
|
||||
score := 0
|
||||
m := make(map[string]bool)
|
||||
for i := range findings {
|
||||
f := &findings[i]
|
||||
switch f.Outcome {
|
||||
case finding.OutcomeNotApplicable:
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: finding.FileTypeSource,
|
||||
Offset: 1,
|
||||
Text: f.Message,
|
||||
})
|
||||
case finding.OutcomePositive:
|
||||
switch f.Probe {
|
||||
case hasFSFOrOSIApprovedLicense.Probe:
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: finding.FileTypeSource,
|
||||
Offset: 1,
|
||||
Path: f.Message,
|
||||
Text: "FSF or OSI recognized license",
|
||||
})
|
||||
score += scoreProbeOnce(f.Probe, m, 1)
|
||||
case hasLicenseFileAtTopDir.Probe:
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: finding.FileTypeSource,
|
||||
Offset: 1,
|
||||
Path: f.Message,
|
||||
Text: "License file found in expected location",
|
||||
})
|
||||
score += scoreProbeOnce(f.Probe, m, 3)
|
||||
case hasLicenseFile.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 6)
|
||||
default:
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "unknown probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
case finding.OutcomeNegative:
|
||||
switch f.Probe {
|
||||
case hasLicenseFileAtTopDir.Probe:
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Type: finding.FileTypeSource,
|
||||
Offset: 1,
|
||||
Path: f.Message,
|
||||
Text: "License file found in unexpected location",
|
||||
})
|
||||
case hasFSFOrOSIApprovedLicense.Probe:
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Type: finding.FileTypeSource,
|
||||
Offset: 1,
|
||||
Path: "",
|
||||
Text: f.Message,
|
||||
})
|
||||
}
|
||||
default:
|
||||
continue // for linting
|
||||
}
|
||||
}
|
||||
_, defined := m[hasLicenseFile.Probe]
|
||||
if !defined {
|
||||
if score > 0 {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "score calculation problem")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
return checker.CreateMinScoreResult(name, "license file not detected")
|
||||
}
|
||||
|
||||
// TODO: although this a loop, the raw checks will only return one licence file
|
||||
// when more than one license file can be aggregated into a composite
|
||||
// score, that logic can be comprehended here.
|
||||
score = 0
|
||||
for idx := range r.LicenseFiles {
|
||||
score = scoreLicenseCriteria(&r.LicenseFiles[idx], dl)
|
||||
}
|
||||
|
||||
return checker.CreateResultWithScore(name, "license file detected", score)
|
||||
}
|
||||
|
@ -16,147 +16,136 @@ package evaluation
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
scut "github.com/ossf/scorecard/v4/utests"
|
||||
)
|
||||
|
||||
func Test_scoreLicenseCriteria(t *testing.T) {
|
||||
t.Parallel()
|
||||
type args struct {
|
||||
f *checker.LicenseFile
|
||||
dl checker.DetailLogger
|
||||
}
|
||||
tests := []struct { //nolint:govet
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "License Attribution Type API",
|
||||
args: args{
|
||||
f: &checker.LicenseFile{
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeAPI,
|
||||
Approved: true,
|
||||
},
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "License Attribution Type Heuristics",
|
||||
args: args{
|
||||
f: &checker.LicenseFile{
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeHeuristics,
|
||||
},
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 9,
|
||||
},
|
||||
{
|
||||
name: "License Attribution Type Other",
|
||||
args: args{
|
||||
f: &checker.LicenseFile{
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeOther,
|
||||
},
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 6,
|
||||
},
|
||||
{
|
||||
name: "License Attribution Type Unknown",
|
||||
args: args{
|
||||
f: &checker.LicenseFile{
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: "Unknown",
|
||||
},
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 6,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Parallel testing scoping hack.
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := scoreLicenseCriteria(tt.args.f, tt.args.dl); got != tt.want {
|
||||
t.Errorf("scoreLicenseCriteria() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicense(t *testing.T) {
|
||||
t.Parallel()
|
||||
type args struct { //nolint:govet
|
||||
name string
|
||||
dl checker.DetailLogger
|
||||
r *checker.LicenseData
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want checker.CheckResult
|
||||
name string
|
||||
findings []finding.Finding
|
||||
result scut.TestReturn
|
||||
}{
|
||||
{
|
||||
name: "No License",
|
||||
args: args{
|
||||
name: "No License",
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: -1,
|
||||
Version: 2,
|
||||
Reason: "internal error: empty raw data",
|
||||
Name: "No License",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No License Files",
|
||||
args: args{
|
||||
name: "No License Files",
|
||||
dl: &scut.TestDetailLogger{},
|
||||
r: &checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
name: "Positive outcome = Max Score",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasLicenseFileAtTopDir",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 0,
|
||||
Version: 2,
|
||||
Reason: "license file not detected",
|
||||
Name: "No License Files",
|
||||
result: scut.TestReturn{
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfInfo: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License Files Detected",
|
||||
args: args{
|
||||
name: "License Files Detected",
|
||||
dl: &scut.TestDetailLogger{},
|
||||
r: &checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeAPI,
|
||||
Approved: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Negative outcomes from all probes = Min score",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "hasLicenseFileAtTopDir",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 10,
|
||||
Version: 2,
|
||||
Reason: "license file detected",
|
||||
Name: "License Files Detected",
|
||||
result: scut.TestReturn{
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfWarn: 2,
|
||||
},
|
||||
}, {
|
||||
name: "Has license file but not a top level or in OSI/FSF format",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "hasLicenseFileAtTopDir",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: 6,
|
||||
NumberOfWarn: 2,
|
||||
},
|
||||
}, {
|
||||
name: "Findings missing a probe = Error",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: -1,
|
||||
Error: sce.ErrScorecardInternal,
|
||||
},
|
||||
}, {
|
||||
name: "Has a license at top dir but it is not OSI/FSF approved",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "hasLicenseFileAtTopDir",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: 9,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfWarn: 1,
|
||||
},
|
||||
}, {
|
||||
name: "Has an OSI/FSF approved license but not at top level dir",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasLicenseFile",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasFSFOrOSIApprovedLicense",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "hasLicenseFileAtTopDir",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: 7,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfWarn: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -164,8 +153,10 @@ func TestLicense(t *testing.T) {
|
||||
tt := tt // Parallel testing scoping hack.
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := License(tt.args.name, tt.args.dl, tt.args.r); !cmp.Equal(got, tt.want, cmpopts.IgnoreFields(checker.CheckResult{}, "Error")) { //nolint:lll
|
||||
t.Errorf("License() = %v, want %v", got, cmp.Diff(got, tt.want, cmpopts.IgnoreFields(checker.CheckResult{}, "Error"))) //nolint:lll
|
||||
dl := scut.TestDetailLogger{}
|
||||
got := License(tt.name, tt.findings, &dl)
|
||||
if !scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl) {
|
||||
t.Errorf("got %v, expected %v", got, tt.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
"github.com/ossf/scorecard/v4/checks/evaluation"
|
||||
"github.com/ossf/scorecard/v4/checks/raw"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/probes"
|
||||
"github.com/ossf/scorecard/v4/probes/zrunner"
|
||||
)
|
||||
|
||||
// CheckLicense is the registered name for License.
|
||||
@ -44,9 +46,15 @@ func License(c *checker.CheckRequest) checker.CheckResult {
|
||||
}
|
||||
|
||||
// Set the raw results.
|
||||
if c.RawResults != nil {
|
||||
c.RawResults.LicenseResults = rawData
|
||||
pRawResults := getRawResults(c)
|
||||
pRawResults.LicenseResults = rawData
|
||||
|
||||
// Evaluate the probes.
|
||||
findings, err := zrunner.Run(pRawResults, probes.License)
|
||||
if err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckLicense, e)
|
||||
}
|
||||
|
||||
return evaluation.License(CheckLicense, c.Dlogger, &rawData)
|
||||
return evaluation.License(CheckLicense, findings, c.Dlogger)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestLicenseFileSubdirectory(t *testing.T) {
|
||||
inputFolder: "testdata/licensedir/withlicense",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore - 1,
|
||||
Score: 9, // Does not have approved format
|
||||
NumberOfInfo: 1,
|
||||
NumberOfWarn: 1,
|
||||
},
|
||||
@ -52,8 +52,10 @@ func TestLicenseFileSubdirectory(t *testing.T) {
|
||||
name: "Without LICENSE",
|
||||
inputFolder: "testdata/licensedir/withoutlicense",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MinResultScore,
|
||||
Error: nil,
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 2,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
|
@ -30,6 +30,9 @@ import (
|
||||
"github.com/ossf/scorecard/v4/probes/fuzzedWithPythonAtheris"
|
||||
"github.com/ossf/scorecard/v4/probes/fuzzedWithRustCargofuzz"
|
||||
"github.com/ossf/scorecard/v4/probes/fuzzedWithSwiftLibFuzzer"
|
||||
"github.com/ossf/scorecard/v4/probes/hasFSFOrOSIApprovedLicense"
|
||||
"github.com/ossf/scorecard/v4/probes/hasLicenseFile"
|
||||
"github.com/ossf/scorecard/v4/probes/hasLicenseFileAtTopDir"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsLinks"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsText"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsVulnerabilityDisclosure"
|
||||
@ -77,6 +80,11 @@ var (
|
||||
fuzzedWithPropertyBasedTypescript.Run,
|
||||
fuzzedWithPropertyBasedJavascript.Run,
|
||||
}
|
||||
License = []ProbeImpl{
|
||||
hasLicenseFile.Run,
|
||||
hasFSFOrOSIApprovedLicense.Run,
|
||||
hasLicenseFileAtTopDir.Run,
|
||||
}
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
@ -85,6 +93,7 @@ func init() {
|
||||
DependencyToolUpdates,
|
||||
SecurityPolicy,
|
||||
Fuzzing,
|
||||
License,
|
||||
})
|
||||
}
|
||||
|
||||
|
28
probes/hasFSFOrOSIApprovedLicense/def.yml
Normal file
28
probes/hasFSFOrOSIApprovedLicense/def.yml
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2023 OpenSSF 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.
|
||||
|
||||
id: hasFSFOrOSIApprovedLicense
|
||||
short: Check that the project has an FSF or OSI approved license. For more, see [all FSF or OSI approved license formats](https://spdx.org/licenses/).
|
||||
motivation: >
|
||||
A license can give users information about how the source code may or may not be used. The lack of a license will impede any kind of security review or audit and creates a legal risk for potential users.
|
||||
implementation: >
|
||||
The implementation checks whether a license file is present and is of an approved format
|
||||
outcome:
|
||||
- If a license file is found and is of an approved format, the probe returns a single OutcomePositive.
|
||||
- If a license file is missing the probe returns a single OutcomeNotApplicable.
|
||||
- If the license is not of an approved format, the probe returns a single OutcomeNegative.
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- Update the license file format in the Github repository to be of an approved format.
|
69
probes/hasFSFOrOSIApprovedLicense/impl.go
Normal file
69
probes/hasFSFOrOSIApprovedLicense/impl.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasFSFOrOSIApprovedLicense
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "hasFSFOrOSIApprovedLicense"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
|
||||
}
|
||||
|
||||
if raw.LicenseResults.LicenseFiles == nil || len(raw.LicenseResults.LicenseFiles) == 0 {
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
"project does not have a license file", nil,
|
||||
finding.OutcomeNotApplicable)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
||||
|
||||
for _, licenseFile := range raw.LicenseResults.LicenseFiles {
|
||||
if licenseFile.LicenseInformation.Approved {
|
||||
// Store the file path in the msg
|
||||
msg := licenseFile.File.Path
|
||||
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
msg, nil,
|
||||
finding.OutcomePositive)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
||||
}
|
||||
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
"project license file does not contain an FSF or OSI license.", nil,
|
||||
finding.OutcomeNegative)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
166
probes/hasFSFOrOSIApprovedLicense/impl_test.go
Normal file
166
probes/hasFSFOrOSIApprovedLicense/impl_test.go
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasFSFOrOSIApprovedLicense
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "License file found and is approved: outcome should be positive",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Approved: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file found and is not approved: outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Approved: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file not found and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file found but is not approved. Outcome should be Negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: "wrong attribution",
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "COPYING.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: "wrong attribution2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil license files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "0 license files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
findings, s, err := Run(tt.raw)
|
||||
if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) {
|
||||
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors()))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(Probe, s); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(len(tt.outcomes), len(findings)); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
for i := range tt.outcomes {
|
||||
outcome := &tt.outcomes[i]
|
||||
f := &findings[i]
|
||||
if diff := cmp.Diff(*outcome, f.Outcome); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
29
probes/hasLicenseFile/def.yml
Normal file
29
probes/hasLicenseFile/def.yml
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2023 OpenSSF 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.
|
||||
|
||||
id: hasLicenseFile
|
||||
short: Check that the project has a license file
|
||||
motivation: >
|
||||
A license can give users information about how the source code may or may not be used. The lack of a license will impede any kind of security review or audit and creates a legal risk for potential users.
|
||||
implementation: >
|
||||
The implementation checks whether a license file is present.
|
||||
outcome:
|
||||
- If license files are found, the probe returns OutcomePositive for each license file.
|
||||
- If a license file is not found, the probe returns a single OutcomeNegative.
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- For Github projects, follow [this guide](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository) to determine which license to apply to your project and establish a license file for your project.
|
||||
- For Gitlab projects, create the license in a .adoc, .asc, .docx, .doc, .ext, .html, .markdown, .md, .rst, .txt, or .xml, named LICENSE, COPYRIGHT, or COPYING, and place it in the top-level directory. To identify a specific license, use an SPDX license identifier in the filename. Examples include LICENSE.md, Apache-2.0-LICENSE.md or LICENSE-Apache-2.0.
|
||||
- Alternately, create a LICENSE directory and add a license file(s) with a name that matches your SPDX license identifier. such as LICENSES/Apache-2.0.txt.
|
76
probes/hasLicenseFile/impl.go
Normal file
76
probes/hasLicenseFile/impl.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasLicenseFile
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "hasLicenseFile"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
|
||||
}
|
||||
|
||||
var findings []finding.Finding
|
||||
var outcome finding.Outcome
|
||||
var msg string
|
||||
|
||||
licenseFiles := raw.LicenseResults.LicenseFiles
|
||||
|
||||
if len(licenseFiles) == 0 {
|
||||
outcome = finding.OutcomeNegative
|
||||
msg = "project does not have a license file"
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
msg, nil,
|
||||
outcome)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
return findings, Probe, nil
|
||||
} else {
|
||||
for _, licenseFile := range licenseFiles {
|
||||
licenseFile := licenseFile
|
||||
loc := &finding.Location{
|
||||
Type: licenseFile.File.Type,
|
||||
Path: licenseFile.File.Path,
|
||||
LineStart: &licenseFile.File.Offset,
|
||||
LineEnd: &licenseFile.File.EndOffset,
|
||||
Snippet: &licenseFile.File.Snippet,
|
||||
}
|
||||
msg = "project has a license file"
|
||||
outcome = finding.OutcomePositive
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
msg, loc,
|
||||
outcome)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
}
|
||||
return findings, Probe, nil
|
||||
}
|
126
probes/hasLicenseFile/impl_test.go
Normal file
126
probes/hasLicenseFile/impl_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasLicenseFile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "License file found and outcome should be positive",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file not found and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil license files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "0 license files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file is nil",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
findings, s, err := Run(tt.raw)
|
||||
if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) {
|
||||
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors()))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(Probe, s); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(len(tt.outcomes), len(findings)); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
for i := range tt.outcomes {
|
||||
outcome := &tt.outcomes[i]
|
||||
f := &findings[i]
|
||||
if diff := cmp.Diff(*outcome, f.Outcome); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
27
probes/hasLicenseFileAtTopDir/def.yml
Normal file
27
probes/hasLicenseFileAtTopDir/def.yml
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright 2023 OpenSSF 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.
|
||||
|
||||
id: hasLicenseFileAtTopDir
|
||||
short: Check that the project has a license file
|
||||
motivation: >
|
||||
A license can give users information about how the source code may or may not be used. The lack of a license will impede any kind of security review or audit and creates a legal risk for potential users.
|
||||
implementation: >
|
||||
This check will detect files in the top-level directory with any combination of the following names and extensions: LICENSE, LICENCE, COPYING, COPYRIGHT and having common extensions such as .html, .txt, or .md. It will also detect these files in a directory named LICENSES in the top directory. (Files in a LICENSES directory are typically named as their SPDX license identifier followed by an appropriate file extension, as described in the [REUSE Specification](https://reuse.software/spec/).)
|
||||
outcome:
|
||||
- If the projects license file is found at the top level, the probe returns a single OutcomePositive (1).
|
||||
- If the projects license file is not found at the top level, the probe returns a single OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- Place the license file at the top level of the project source tree.
|
83
probes/hasLicenseFileAtTopDir/impl.go
Normal file
83
probes/hasLicenseFileAtTopDir/impl.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasLicenseFileAtTopDir
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "hasLicenseFileAtTopDir"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
|
||||
}
|
||||
|
||||
if raw.LicenseResults.LicenseFiles == nil || len(raw.LicenseResults.LicenseFiles) == 0 {
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
"project does not have a license file", nil,
|
||||
finding.OutcomeNotApplicable)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
||||
|
||||
for _, licenseFile := range raw.LicenseResults.LicenseFiles {
|
||||
switch licenseFile.LicenseInformation.Attribution {
|
||||
case checker.LicenseAttributionTypeAPI, checker.LicenseAttributionTypeHeuristics:
|
||||
// both repoAPI and scorecard (not using the API) follow checks.md
|
||||
// for a file to be found it must have been in the correct location
|
||||
// award location points.
|
||||
|
||||
// Store the file path in the msg
|
||||
msg := licenseFile.File.Path
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
msg, nil,
|
||||
finding.OutcomePositive)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
|
||||
case checker.LicenseAttributionTypeOther:
|
||||
msg := "License file found in unexpected location"
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
msg, nil,
|
||||
finding.OutcomeNegative)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
||||
}
|
||||
|
||||
f, err := finding.NewWith(fs, Probe,
|
||||
"Did not find the license file at the expected location.", nil,
|
||||
finding.OutcomeNegative)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
return []finding.Finding{*f}, Probe, nil
|
||||
}
|
158
probes/hasLicenseFileAtTopDir/impl_test.go
Normal file
158
probes/hasLicenseFileAtTopDir/impl_test.go
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright 2023 OpenSSF 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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package hasLicenseFileAtTopDir
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "License file found and correct attribution 1",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeAPI,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file not found and outcome should be OutcomeNotApplicable",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file found and correct attribution 2",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: checker.LicenseAttributionTypeHeuristics,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "License file found and wrong attribution",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "LICENSE.md",
|
||||
},
|
||||
LicenseInformation: checker.License{
|
||||
Attribution: "wrong_attribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil license files and outcome should be OutcomeNotApplicable",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "0 license files and outcome should be OutcomeNotApplicable",
|
||||
raw: &checker.RawResults{
|
||||
LicenseResults: checker.LicenseData{
|
||||
LicenseFiles: []checker.LicenseFile{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNotApplicable,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
findings, s, err := Run(tt.raw)
|
||||
if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) {
|
||||
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors()))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(Probe, s); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(len(tt.outcomes), len(findings)); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
for i := range tt.outcomes {
|
||||
outcome := &tt.outcomes[i]
|
||||
f := &findings[i]
|
||||
if diff := cmp.Diff(*outcome, f.Outcome); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user