Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added probe for permissive licenses #3838

Merged
merged 13 commits into from
Apr 12, 2024
Merged
79 changes: 79 additions & 0 deletions checks/evaluation/permissive_license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2024 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.

package evaluation

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/hasPermissiveLicense"
)

// License applies the score policy for the License check.
func PermissiveLicense(name string,
findings []finding.Finding,
dl checker.DetailLogger,
) checker.CheckResult {
// We have 3 unique probes, each should have a finding.
expectedProbes := []string{
hasPermissiveLicense.Probe,
}

if !finding.UniqueProbesEqual(findings, expectedProbes) {
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")
return checker.CreateRuntimeErrorResult(name, e)
}

// 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 hasPermissiveLicense.Probe:
score += scoreProbeOnce(f.Probe, m, 10)
default:
e := sce.WithMessage(sce.ErrScorecardInternal, "unknown probe results ")
return checker.CreateRuntimeErrorResult(name, e)
}
case finding.OutcomeNegative:
switch f.Probe {
case hasPermissiveLicense.Probe:
dl.Info(&checker.LogMessage{
Text: "Non-permissive license found " + f.Message,
})
}
default:
continue // for linting
}
}
_, defined := m[hasPermissiveLicense.Probe]
if !defined {
if score > 0 {
e := sce.WithMessage(sce.ErrScorecardInternal, "score calculation problem")
return checker.CreateRuntimeErrorResult(name, e)
}
return checker.CreateMinScoreResult(name, "Non-Permissive license detected")
}
return checker.CreateMaxScoreResult(name, "Permissive license detected")
}
75 changes: 75 additions & 0 deletions checks/evaluation/permissive_license_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2024 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.

package evaluation
fhoeborn marked this conversation as resolved.
Show resolved Hide resolved

import (
"testing"

"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 TestPermissiveLicense(t *testing.T) {
t.Parallel()
tests := []struct {
name string
findings []finding.Finding
result scut.TestReturn
}{
{
name: "Positive outcome = Max Score",
findings: []finding.Finding{
{
Probe: "hasPermissiveLicense",
Outcome: finding.OutcomePositive,
},
},
result: scut.TestReturn{
Score: checker.MaxResultScore,
NumberOfInfo: 0,
},
}, {
name: "Negative outcomes from all probes = Min score",
findings: []finding.Finding{
{
Probe: "hasPermissiveLicense",
Outcome: finding.OutcomeNegative,
},
},
result: scut.TestReturn{
Score: checker.MinResultScore,
NumberOfInfo: 1,
},
}, {
name: "Findings missing a probe = Error",
findings: []finding.Finding{},
result: scut.TestReturn{
Score: -1,
Error: sce.ErrScorecardInternal,
},
},
}
for _, tt := range tests {
tt := tt // Parallel testing scoping hack.
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
dl := scut.TestDetailLogger{}
got := PermissiveLicense(tt.name, tt.findings, &dl)
scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl)
})
}
}
60 changes: 60 additions & 0 deletions checks/permissive_license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 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.

package checks

import (
"github.com/ossf/scorecard/v4/checker"
"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"
)

// CheckPermissiveLicense is the registered name for PermissiveLicense.
const CheckPermissiveLicense = "PermissiveLicense"

//nolint:gochecknoinits
func init() {
supportedRequestTypes := []checker.RequestType{
checker.CommitBased,
}
if err := registerCheck(CheckPermissiveLicense, PermissiveLicense, supportedRequestTypes); err != nil {
// this should never happen
panic(err)
}
}

// PermissiveLicense runs PermissiveLicense check.
func PermissiveLicense(c *checker.CheckRequest) checker.CheckResult {
rawData, err := raw.License(c)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckPermissiveLicense, e)
}

// Set the raw results.
pRawResults := getRawResults(c)
pRawResults.LicenseResults = rawData
spencerschrock marked this conversation as resolved.
Show resolved Hide resolved

// Evaluate the probes.
findings, err := zrunner.Run(pRawResults, probes.PermissiveLicense)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckPermissiveLicense, e)
}

return evaluation.PermissiveLicense(CheckPermissiveLicense, findings, c.Dlogger)
}
103 changes: 103 additions & 0 deletions checks/permissive_license_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2024 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.

package checks

import (
"context"
"testing"

"github.com/golang/mock/gomock"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/clients"
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
scut "github.com/ossf/scorecard/v4/utests"
)

func TestPermissiveLicenseFileSubdirectory(t *testing.T) {
t.Parallel()

tests := []struct {
name string
license clients.License
err error
expected scut.TestReturn
}{
{
name: "With permissive LICENSE",
license: clients.License{
Key: "Apache-2.0",
Name: "Apache 2.0",
Path: "testdata/licensedir/withpermissivelicense/LICENSE",
SPDXId: "Apache-2.0",
Type: "Permissive",
Size: 42,
},
expected: scut.TestReturn{
Error: nil,
Score: checker.MaxResultScore,
NumberOfInfo: 0,
NumberOfWarn: 0,
},
err: nil,
},
{
name: "Without permissive LICENSE",
license: clients.License{
Key: "AGPL-3.0",
Name: "AGPL 3.0",
Path: "testdata/licensedir/withcopyleftlicense/LICENSE",
SPDXId: "AGPL-3.0",
Type: "Copyleft",
Size: 42,
},
expected: scut.TestReturn{
Error: nil,
Score: checker.MinResultScore,
NumberOfWarn: 0,
NumberOfInfo: 1,
},
err: nil,
},
}
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()

// TODO: Use gMock instead of Localdir here.
ctrl := gomock.NewController(t)
mockRepo := mockrepo.NewMockRepoClient(ctrl)

mockRepo.EXPECT().ListLicenses().Return([]clients.License{tt.license}, nil).AnyTimes()

ctx := context.Background()

dl := scut.TestDetailLogger{}

req := checker.CheckRequest{
Ctx: ctx,
RepoClient: mockRepo,
Dlogger: &dl,
}

res := PermissiveLicense(&req)

scut.ValidateTestReturn(t, tt.name, &tt.expected, &res, &dl)

ctrl.Finish()
})
}
}
38 changes: 38 additions & 0 deletions docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,44 @@ issue](https://github.com/ossf/scorecard/issues/new/choose).
- Publish your project as a downloadable package, e.g., if hosted on GitHub, use [GitHub's mechanisms for publishing a package](https://docs.github.com/en/packages/learn-github-packages/publishing-a-package).
- If hosted on GitHub, use a GitHub action to release your package to language-specific hubs.

## PermissiveLicense

Risk: `Low` (possible impediment to security review)

This check tries to determine if the project has published a license and if this license is permissive. It
works by using either hosting APIs or by checking standard locations
for a file named according to common conventions for licenses.

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.
A permissive license gives the source code user further options to use the
source code in his own derivative works without publishing his source code,
which is often not wanted in enterprise scenarios.

Scorecard uses the
[GitHub License API](https://docs.github.com/en/rest/licenses#get-the-license-for-a-repository)
for GitHub hosted projects. Otherwise, Scorecard uses its own heuristics to
detect a published license file.

On its own, 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`.
(Files in a `LICENSES` directory are typically named as their
[SPDX](https://spdx.org/licenses/) license identifier followed by an
appropriate file extension, as described in the [REUSE](https://reuse.software/spec/) Specification.)

Permissive License Requirements:
- The detected license is permissive (e.g. Apache 2.0, BSD(0-3 Clause), MIT) (10/10 points)


**Remediation steps**
- Determine [which license](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository) to apply to your project. For GitHub hosted projects, follow those instructions to establish a license for your project.
- For other hosting environments, 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](https://spdx.org/licenses/) 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](https://spdx.org/licenses/). such as `LICENSES/Apache-2.0.txt`.
- Declare a permissive license (see for example https://fossa.com/blog/all-about-permissive-licenses/)

## Pinned-Dependencies

Risk: `Medium` (possible compromised dependencies)
Expand Down
Loading