diff options
author | Keith Zantow <kzantow@gmail.com> | 2022-10-07 15:36:32 -0400 |
---|---|---|
committer | Keith Zantow <kzantow@gmail.com> | 2022-10-07 15:36:32 -0400 |
commit | b57c493f0652477bf3764be7273eab6ff11d1eb1 (patch) | |
tree | 2ce0bc41c7f9b6c9a40715565c49c1cbf190716d | |
parent | be2eb824595afe28c87c9a33c2da565480d1eb54 (diff) | |
download | spdx-tools-b57c493f0652477bf3764be7273eab6ff11d1eb1.tar.gz |
chore: add builder and verification functions for 2.3
Signed-off-by: Keith Zantow <kzantow@gmail.com>
-rw-r--r-- | builder/build.go | 71 | ||||
-rw-r--r-- | builder/builder2v3/build_creation_info.go | 47 | ||||
-rw-r--r-- | builder/builder2v3/build_creation_info_test.go | 111 | ||||
-rw-r--r-- | builder/builder2v3/build_file.go | 56 | ||||
-rw-r--r-- | builder/builder2v3/build_file_test.go | 74 | ||||
-rw-r--r-- | builder/builder2v3/build_package.go | 79 | ||||
-rw-r--r-- | builder/builder2v3/build_package_test.go | 156 | ||||
-rw-r--r-- | builder/builder2v3/build_relationship.go | 24 | ||||
-rw-r--r-- | builder/builder2v3/build_relationship_test.go | 33 | ||||
-rw-r--r-- | utils/verification.go | 40 | ||||
-rw-r--r-- | utils/verification_test.go | 165 |
11 files changed, 856 insertions, 0 deletions
diff --git a/builder/build.go b/builder/build.go index 1594a04..3c670a0 100644 --- a/builder/build.go +++ b/builder/build.go @@ -9,9 +9,11 @@ import ( "github.com/spdx/tools-golang/builder/builder2v1" "github.com/spdx/tools-golang/builder/builder2v2" + "github.com/spdx/tools-golang/builder/builder2v3" "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // ===== 2.1 builder ===== @@ -151,3 +153,72 @@ func Build2_2(packageName string, dirRoot string, config *Config2_2) (*v2_2.Docu return doc, nil } + +// ===== 2.3 builder ===== + +// Config2_3 is a collection of configuration settings for builder +// (for version 2.3 SPDX Documents). A few mandatory fields are set here +// so that they can be repeatedly reused in multiple calls to Build2_3. +type Config2_3 struct { + // NamespacePrefix should be a URI representing a prefix for the + // namespace with which the SPDX Document will be associated. + // It will be used in the DocumentNamespace field in the CreationInfo + // section, followed by the per-Document package name and a random UUID. + NamespacePrefix string + + // CreatorType should be one of "Person", "Organization" or "Tool". + // If not one of those strings, it will be interpreted as "Person". + CreatorType string + + // Creator will be filled in for the given CreatorType. + Creator string + + // PathsIgnored lists certain paths to be omitted from the built document. + // Each string should be a path, relative to the package's dirRoot, + // to a specific file or (for all files in a directory) ending in a slash. + // Prefix the string with "**" to omit all instances of that file / + // directory, regardless of where it is in the file tree. + PathsIgnored []string + + // TestValues is used to pass fixed values for testing purposes + // only, and should be set to nil for production use. It is only + // exported so that it will be accessible within builder2v3. + TestValues map[string]string +} + +// Build2_3 creates an SPDX Document (version 2.3), returning that document or +// error if any is encountered. Arguments: +// - packageName: name of package / directory +// - dirRoot: path to directory to be analyzed +// - config: Config object +func Build2_3(packageName string, dirRoot string, config *Config2_3) (*v2_3.Document, error) { + // build Package section first -- will include Files and make the + // package verification code available + pkg, err := builder2v3.BuildPackageSection2_3(packageName, dirRoot, config.PathsIgnored) + if err != nil { + return nil, err + } + + ci, err := builder2v3.BuildCreationInfoSection2_3(config.CreatorType, config.Creator, config.TestValues) + if err != nil { + return nil, err + } + + rln, err := builder2v3.BuildRelationshipSection2_3(packageName) + if err != nil { + return nil, err + } + + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.3", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + DocumentName: packageName, + DocumentNamespace: fmt.Sprintf("%s%s-%s", config.NamespacePrefix, packageName, pkg.PackageVerificationCode), + CreationInfo: ci, + Packages: []*v2_3.Package{pkg}, + Relationships: []*v2_3.Relationship{rln}, + } + + return doc, nil +} diff --git a/builder/builder2v3/build_creation_info.go b/builder/builder2v3/build_creation_info.go new file mode 100644 index 0000000..04a8e16 --- /dev/null +++ b/builder/builder2v3/build_creation_info.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "time" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// BuildCreationInfoSection2_3 creates an SPDX Package (version 2.3), returning that +// package or error if any is encountered. Arguments: +// - packageName: name of package / directory +// - code: verification code from Package +// - namespacePrefix: prefix for DocumentNamespace (packageName and code will be added) +// - creatorType: one of Person, Organization or Tool +// - creator: creator string +// - testValues: for testing only; call with nil when using in production +func BuildCreationInfoSection2_3(creatorType string, creator string, testValues map[string]string) (*v2_3.CreationInfo, error) { + // build creator slices + creators := []common.Creator{ + // add builder as a tool + { + Creator: "github.com/spdx/tools-golang/builder", + CreatorType: "Tool", + }, + { + Creator: creator, + CreatorType: creatorType, + }, + } + + // use test Created time if passing test values + location, _ := time.LoadLocation("UTC") + locationTime := time.Now().In(location) + created := locationTime.Format("2006-01-02T15:04:05Z") + if testVal := testValues["Created"]; testVal != "" { + created = testVal + } + + ci := &v2_3.CreationInfo{ + Creators: creators, + Created: created, + } + return ci, nil +} diff --git a/builder/builder2v3/build_creation_info_test.go b/builder/builder2v3/build_creation_info_test.go new file mode 100644 index 0000000..f1868f0 --- /dev/null +++ b/builder/builder2v3/build_creation_info_test.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "testing" +) + +// ===== CreationInfo section builder tests ===== +func TestBuilder2_3CanBuildCreationInfoSection(t *testing.T) { + creatorType := "Organization" + creator := "Jane Doe LLC" + testValues := make(map[string]string) + testValues["Created"] = "2018-10-20T16:48:00Z" + + ci, err := BuildCreationInfoSection2_3(creatorType, creator, testValues) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if ci == nil { + t.Fatalf("expected non-nil CreationInfo, got nil") + } + if len(ci.Creators) != 2 { + t.Fatalf("expected %d, got %d", 2, len(ci.Creators)) + } + if ci.Creators[1].Creator != "Jane Doe LLC" { + t.Errorf("expected %s, got %s", "Jane Doe LLC", ci.Creators[0].Creator) + } + if ci.Creators[0].Creator != "github.com/spdx/tools-golang/builder" { + t.Errorf("expected %s, got %s", "github.com/spdx/tools-golang/builder", ci.Creators[1].Creator) + } + if ci.Created != "2018-10-20T16:48:00Z" { + t.Errorf("expected %s, got %s", "2018-10-20T16:48:00Z", ci.Created) + } +} + +func TestBuilder2_3CanBuildCreationInfoSectionWithCreatorPerson(t *testing.T) { + creatorType := "Person" + creator := "John Doe" + testValues := make(map[string]string) + testValues["Created"] = "2018-10-20T16:48:00Z" + + ci, err := BuildCreationInfoSection2_3(creatorType, creator, testValues) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if ci == nil { + t.Fatalf("expected non-nil CreationInfo, got nil") + } + if len(ci.Creators) != 2 { + t.Fatalf("expected %d, got %d", 2, len(ci.Creators)) + } + if ci.Creators[1].Creator != "John Doe" { + t.Errorf("expected %s, got %s", "John Doe", ci.Creators[0].Creator) + } + if ci.Creators[0].Creator != "github.com/spdx/tools-golang/builder" { + t.Errorf("expected %s, got %s", "github.com/spdx/tools-golang/builder", ci.Creators[1].Creator) + } +} + +func TestBuilder2_3CanBuildCreationInfoSectionWithCreatorTool(t *testing.T) { + creatorType := "Tool" + creator := "some-other-tool-2.1" + testValues := make(map[string]string) + testValues["Created"] = "2018-10-20T16:48:00Z" + + ci, err := BuildCreationInfoSection2_3(creatorType, creator, testValues) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if ci == nil { + t.Fatalf("expected non-nil CreationInfo, got nil") + } + if len(ci.Creators) != 2 { + t.Fatalf("expected %d, got %d", 2, len(ci.Creators)) + } + if ci.Creators[0].Creator != "github.com/spdx/tools-golang/builder" { + t.Errorf("expected %s, got %s", "github.com/spdx/tools-golang/builder", ci.Creators[0]) + } + if ci.Creators[1].Creator != "some-other-tool-2.1" { + t.Errorf("expected %s, got %s", "some-other-tool-2.1", ci.Creators[1]) + } +} + +func TestBuilder2_3CanBuildCreationInfoSectionWithInvalidPerson(t *testing.T) { + creatorType := "Whatever" + creator := "John Doe" + testValues := make(map[string]string) + testValues["Created"] = "2018-10-20T16:48:00Z" + + ci, err := BuildCreationInfoSection2_3(creatorType, creator, testValues) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if ci == nil { + t.Fatalf("expected non-nil CreationInfo, got nil") + } + if len(ci.Creators) != 2 { + t.Fatalf("expected %d, got %d", 2, len(ci.Creators)) + } + if ci.Creators[1].Creator != "John Doe" { + t.Errorf("expected %s, got %s", "John Doe", ci.Creators[1]) + } + if ci.Creators[0].Creator != "github.com/spdx/tools-golang/builder" { + t.Errorf("expected %s, got %s", "github.com/spdx/tools-golang/builder", ci.Creators[0]) + } +} diff --git a/builder/builder2v3/build_file.go b/builder/builder2v3/build_file.go new file mode 100644 index 0000000..bce8e66 --- /dev/null +++ b/builder/builder2v3/build_file.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "fmt" + "path/filepath" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" + "github.com/spdx/tools-golang/utils" +) + +// BuildFileSection2_3 creates an SPDX File (version 2.3), returning that +// file or error if any is encountered. Arguments: +// - filePath: path to file, relative to prefix +// - prefix: relative directory for filePath +// - fileNumber: integer index (unique within package) to use in identifier +func BuildFileSection2_3(filePath string, prefix string, fileNumber int) (*v2_3.File, error) { + // build the full file path + p := filepath.Join(prefix, filePath) + + // make sure we can get the file and its hashes + ssha1, ssha256, smd5, err := utils.GetHashesForFilePath(p) + if err != nil { + return nil, err + } + + // build the identifier + i := fmt.Sprintf("File%d", fileNumber) + + // now build the File section + f := &v2_3.File{ + FileName: filePath, + FileSPDXIdentifier: common.ElementID(i), + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: ssha1, + }, + { + Algorithm: common.SHA256, + Value: ssha256, + }, + { + Algorithm: common.MD5, + Value: smd5, + }, + }, + LicenseConcluded: "NOASSERTION", + LicenseInfoInFiles: []string{"NOASSERTION"}, + FileCopyrightText: "NOASSERTION", + } + + return f, nil +} diff --git a/builder/builder2v3/build_file_test.go b/builder/builder2v3/build_file_test.go new file mode 100644 index 0000000..c3eaee6 --- /dev/null +++ b/builder/builder2v3/build_file_test.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "testing" + + "github.com/spdx/tools-golang/spdx/common" +) + +// ===== File section builder tests ===== +func TestBuilder2_3CanBuildFileSection(t *testing.T) { + filePath := "/file1.testdata.txt" + prefix := "../../testdata/project1/" + fileNumber := 17 + + file1, err := BuildFileSection2_3(filePath, prefix, fileNumber) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if file1 == nil { + t.Fatalf("expected non-nil file, got nil") + } + if file1.FileName != "/file1.testdata.txt" { + t.Errorf("expected %v, got %v", "/file1.testdata.txt", file1.FileName) + } + if file1.FileSPDXIdentifier != common.ElementID("File17") { + t.Errorf("expected %v, got %v", "File17", file1.FileSPDXIdentifier) + } + + for _, checksum := range file1.Checksums { + switch checksum.Algorithm { + case common.SHA1: + if checksum.Value != "024f870eb6323f532515f7a09d5646a97083b819" { + t.Errorf("expected %v, got %v", "024f870eb6323f532515f7a09d5646a97083b819", checksum.Value) + } + case common.SHA256: + if checksum.Value != "b14e44284ca477b4c0db34b15ca4c454b2947cce7883e22321cf2984050e15bf" { + t.Errorf("expected %v, got %v", "b14e44284ca477b4c0db34b15ca4c454b2947cce7883e22321cf2984050e15bf", checksum.Value) + } + case common.MD5: + if checksum.Value != "37c8208479dfe42d2bb29debd6e32d4a" { + t.Errorf("expected %v, got %v", "37c8208479dfe42d2bb29debd6e32d4a", checksum.Value) + } + } + } + + if file1.LicenseConcluded != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", file1.LicenseConcluded) + } + if len(file1.LicenseInfoInFiles) != 1 { + t.Errorf("expected %v, got %v", 1, len(file1.LicenseInfoInFiles)) + } else { + if file1.LicenseInfoInFiles[0] != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", file1.LicenseInfoInFiles[0]) + } + } + if file1.FileCopyrightText != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", file1.FileCopyrightText) + } + +} + +func TestBuilder2_3BuildFileSectionFailsForInvalidFilePath(t *testing.T) { + filePath := "/file1.testdata.txt" + prefix := "oops/wrong/path" + fileNumber := 11 + + _, err := BuildFileSection2_3(filePath, prefix, fileNumber) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} diff --git a/builder/builder2v3/build_package.go b/builder/builder2v3/build_package.go new file mode 100644 index 0000000..f278a59 --- /dev/null +++ b/builder/builder2v3/build_package.go @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "fmt" + "path/filepath" + "regexp" + "runtime" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" + "github.com/spdx/tools-golang/utils" +) + +// BuildPackageSection2_3 creates an SPDX Package (version 2.3), returning +// that package or error if any is encountered. Arguments: +// - packageName: name of package / directory +// - dirRoot: path to directory to be analyzed +// - pathsIgnore: slice of strings for filepaths to ignore +func BuildPackageSection2_3(packageName string, dirRoot string, pathsIgnore []string) (*v2_3.Package, error) { + // build the file section first, so we'll have it available + // for calculating the package verification code + filepaths, err := utils.GetAllFilePaths(dirRoot, pathsIgnore) + osType := runtime.GOOS + + if err != nil { + return nil, err + } + + re, ok := regexp.Compile("/+") + if ok != nil { + return nil, err + } + dirRootLen := 0 + if osType == "windows" { + dirRootLen = len(dirRoot) + } + + files := []*v2_3.File{} + fileNumber := 0 + for _, fp := range filepaths { + newFilePatch := "" + if osType == "windows" { + newFilePatch = filepath.FromSlash("." + fp[dirRootLen:]) + } else { + newFilePatch = filepath.FromSlash("./" + fp) + } + newFile, err := BuildFileSection2_3(re.ReplaceAllLiteralString(newFilePatch, string(filepath.Separator)), dirRoot, fileNumber) + if err != nil { + return nil, err + } + files = append(files, newFile) + fileNumber++ + } + + // get the verification code + code, err := utils.GetVerificationCode2_3(files, "") + if err != nil { + return nil, err + } + + // now build the package section + pkg := &v2_3.Package{ + PackageName: packageName, + PackageSPDXIdentifier: common.ElementID(fmt.Sprintf("Package-%s", packageName)), + PackageDownloadLocation: "NOASSERTION", + FilesAnalyzed: true, + IsFilesAnalyzedTagPresent: true, + PackageVerificationCode: &code, + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseInfoFromFiles: []string{}, + PackageLicenseDeclared: "NOASSERTION", + PackageCopyrightText: "NOASSERTION", + Files: files, + } + + return pkg, nil +} diff --git a/builder/builder2v3/build_package_test.go b/builder/builder2v3/build_package_test.go new file mode 100644 index 0000000..873e3d5 --- /dev/null +++ b/builder/builder2v3/build_package_test.go @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "testing" + + "github.com/spdx/tools-golang/spdx/common" +) + +// ===== Package section builder tests ===== +func TestBuilder2_3CanBuildPackageSection(t *testing.T) { + packageName := "project1" + dirRoot := "../../testdata/project1/" + + wantVerificationCode := common.PackageVerificationCode{Value: "fc9ac4a370af0a471c2e52af66d6b4cf4e2ba12b"} + + pkg, err := BuildPackageSection2_3(packageName, dirRoot, nil) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if pkg == nil { + t.Fatalf("expected non-nil Package, got nil") + } + if pkg.PackageName != "project1" { + t.Errorf("expected %v, got %v", "project1", pkg.PackageName) + } + if pkg.PackageSPDXIdentifier != common.ElementID("Package-project1") { + t.Errorf("expected %v, got %v", "Package-project1", pkg.PackageSPDXIdentifier) + } + if pkg.PackageDownloadLocation != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", pkg.PackageDownloadLocation) + } + if pkg.FilesAnalyzed != true { + t.Errorf("expected %v, got %v", true, pkg.FilesAnalyzed) + } + if pkg.IsFilesAnalyzedTagPresent != true { + t.Errorf("expected %v, got %v", true, pkg.IsFilesAnalyzedTagPresent) + } + if pkg.PackageVerificationCode.Value != wantVerificationCode.Value { + t.Errorf("expected %v, got %v", wantVerificationCode, pkg.PackageVerificationCode) + } + if pkg.PackageLicenseConcluded != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", pkg.PackageLicenseConcluded) + } + if len(pkg.PackageLicenseInfoFromFiles) != 0 { + t.Errorf("expected %v, got %v", 0, len(pkg.PackageLicenseInfoFromFiles)) + } + if pkg.PackageLicenseDeclared != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", pkg.PackageLicenseDeclared) + } + if pkg.PackageCopyrightText != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", pkg.PackageCopyrightText) + } + + // and make sure we got the right number of files, and check the first one + if pkg.Files == nil { + t.Fatalf("expected non-nil pkg.Files, got nil") + } + if len(pkg.Files) != 5 { + t.Fatalf("expected %d, got %d", 5, len(pkg.Files)) + } + fileEmpty := pkg.Files[0] + if fileEmpty == nil { + t.Fatalf("expected non-nil file, got nil") + } + if fileEmpty.FileName != "./emptyfile.testdata.txt" { + t.Errorf("expected %v, got %v", "./emptyfile.testdata.txt", fileEmpty.FileName) + } + if fileEmpty.FileSPDXIdentifier != common.ElementID("File0") { + t.Errorf("expected %v, got %v", "File0", fileEmpty.FileSPDXIdentifier) + } + for _, checksum := range fileEmpty.Checksums { + switch checksum.Algorithm { + case common.SHA1: + if checksum.Value != "da39a3ee5e6b4b0d3255bfef95601890afd80709" { + t.Errorf("expected %v, got %v", "da39a3ee5e6b4b0d3255bfef95601890afd80709", checksum.Value) + } + case common.SHA256: + if checksum.Value != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" { + t.Errorf("expected %v, got %v", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", checksum.Value) + } + case common.MD5: + if checksum.Value != "d41d8cd98f00b204e9800998ecf8427e" { + t.Errorf("expected %v, got %v", "d41d8cd98f00b204e9800998ecf8427e", checksum.Value) + } + } + } + if fileEmpty.LicenseConcluded != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", fileEmpty.LicenseConcluded) + } + if len(fileEmpty.LicenseInfoInFiles) != 1 { + t.Errorf("expected %v, got %v", 1, len(fileEmpty.LicenseInfoInFiles)) + } else { + if fileEmpty.LicenseInfoInFiles[0] != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", fileEmpty.LicenseInfoInFiles[0]) + } + } + if fileEmpty.FileCopyrightText != "NOASSERTION" { + t.Errorf("expected %v, got %v", "NOASSERTION", fileEmpty.FileCopyrightText) + } +} + +func TestBuilder2_3CanIgnoreFiles(t *testing.T) { + packageName := "project3" + dirRoot := "../../testdata/project3/" + pathsIgnored := []string{ + "**/ignoredir/", + "/excludedir/", + "**/ignorefile.txt", + "/alsoEXCLUDEthis.txt", + } + pkg, err := BuildPackageSection2_3(packageName, dirRoot, pathsIgnored) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + // make sure we got the right files + if pkg.Files == nil { + t.Fatalf("expected non-nil pkg.Files, got nil") + } + if len(pkg.Files) != 5 { + t.Fatalf("expected %d, got %d", 5, len(pkg.Files)) + } + + want := "./dontscan.txt" + got := pkg.Files[0].FileName + if want != got { + t.Errorf("expected %v, got %v", want, got) + } + + want = "./keep/keep.txt" + got = pkg.Files[1].FileName + if want != got { + t.Errorf("expected %v, got %v", want, got) + } + + want = "./keep.txt" + got = pkg.Files[2].FileName + if want != got { + t.Errorf("expected %v, got %v", want, got) + } + + want = "./subdir/keep/dontscan.txt" + got = pkg.Files[3].FileName + if want != got { + t.Errorf("expected %v, got %v", want, got) + } + + want = "./subdir/keep/keep.txt" + got = pkg.Files[4].FileName + if want != got { + t.Errorf("expected %v, got %v", want, got) + } +} diff --git a/builder/builder2v3/build_relationship.go b/builder/builder2v3/build_relationship.go new file mode 100644 index 0000000..45ce5be --- /dev/null +++ b/builder/builder2v3/build_relationship.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "fmt" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// BuildRelationshipSection2_3 creates an SPDX Relationship (version 2.3) +// solely for the document "DESCRIBES" package relationship, returning that +// relationship or error if any is encountered. Arguments: +// - packageName: name of package / directory +func BuildRelationshipSection2_3(packageName string) (*v2_3.Relationship, error) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", fmt.Sprintf("Package-%s", packageName)), + Relationship: "DESCRIBES", + } + + return rln, nil +} diff --git a/builder/builder2v3/build_relationship_test.go b/builder/builder2v3/build_relationship_test.go new file mode 100644 index 0000000..cbf27a7 --- /dev/null +++ b/builder/builder2v3/build_relationship_test.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package builder2v3 + +import ( + "testing" + + "github.com/spdx/tools-golang/spdx/common" +) + +// ===== Relationship section builder tests ===== +func TestBuilder2_3CanBuildRelationshipSection(t *testing.T) { + packageName := "project17" + + rln, err := BuildRelationshipSection2_3(packageName) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + + if rln == nil { + t.Fatalf("expected non-nil relationship, got nil") + } + if rln.RefA != common.MakeDocElementID("", "DOCUMENT") { + t.Errorf("expected %v, got %v", "DOCUMENT", rln.RefA) + } + if rln.RefB != common.MakeDocElementID("", "Package-project17") { + t.Errorf("expected %v, got %v", "Package-project17", rln.RefB) + } + if rln.Relationship != "DESCRIBES" { + t.Errorf("expected %v, got %v", "DESCRIBES", rln.Relationship) + } + +} diff --git a/utils/verification.go b/utils/verification.go index bd6c875..72523b3 100644 --- a/utils/verification.go +++ b/utils/verification.go @@ -12,6 +12,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // GetVerificationCode2_1 takes a slice of files and an optional filename @@ -91,3 +92,42 @@ func GetVerificationCode2_2(files []*v2_2.File, excludeFile string) (common.Pack return code, nil } + +// GetVerificationCode2_3 takes a slice of files and an optional filename +// for an "excludes" file, and returns a Package Verification Code calculated +// according to SPDX spec version 2.3, section 3.9.4. +func GetVerificationCode2_3(files []*v2_3.File, excludeFile string) (common.PackageVerificationCode, error) { + // create slice of strings - unsorted SHA1s for all files + shas := []string{} + for i, f := range files { + if f == nil { + return common.PackageVerificationCode{}, fmt.Errorf("got nil file for identifier %v", i) + } + if f.FileName != excludeFile { + // find the SHA1 hash, if present + for _, checksum := range f.Checksums { + if checksum.Algorithm == common.SHA1 { + shas = append(shas, checksum.Value) + } + } + } + } + + // sort the strings + sort.Strings(shas) + + // concatenate them into one string, with no trailing separators + shasConcat := strings.Join(shas, "") + + // and get its SHA1 value + hsha1 := sha1.New() + hsha1.Write([]byte(shasConcat)) + bs := hsha1.Sum(nil) + + code := common.PackageVerificationCode{ + Value: fmt.Sprintf("%x", bs), + ExcludedFiles: []string{excludeFile}, + } + + return code, nil +} diff --git a/utils/verification_test.go b/utils/verification_test.go index 3fa4ead..beee8b6 100644 --- a/utils/verification_test.go +++ b/utils/verification_test.go @@ -8,6 +8,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // ===== 2.1 Verification code functionality tests ===== @@ -277,3 +278,167 @@ func TestPackage2_2GetVerificationCodeFailsIfNilFileInSlice(t *testing.T) { t.Fatalf("expected non-nil error, got nil") } } + +// ===== 2.3 Verification code functionality tests ===== + +func TestPackage2_3CanGetVerificationCode(t *testing.T) { + files := []*v2_3.File{ + { + FileName: "file2.txt", + FileSPDXIdentifier: "File0", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file1.txt", + FileSPDXIdentifier: "File1", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file3.txt", + FileSPDXIdentifier: "File2", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file5.txt", + FileSPDXIdentifier: "File3", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file4.txt", + FileSPDXIdentifier: "File4", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", + }, + }, + }, + } + + wantCode := common.PackageVerificationCode{Value: "ac924b375119c81c1f08c3e2722044bfbbdcd3dc"} + + gotCode, err := GetVerificationCode2_3(files, "") + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + if wantCode.Value != gotCode.Value { + t.Errorf("expected %v, got %v", wantCode, gotCode) + } + +} + +func TestPackage2_3CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { + files := []*v2_3.File{ + { + FileName: "file1.txt", + FileSPDXIdentifier: "File0", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file2.txt", + FileSPDXIdentifier: "File1", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "thisfile.spdx", + FileSPDXIdentifier: "File2", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", + }, + }, + }, + { + FileName: "file3.txt", + FileSPDXIdentifier: "File3", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + { + FileName: "file4.txt", + FileSPDXIdentifier: "File4", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + } + + wantCode := common.PackageVerificationCode{Value: "17fab1bd18fe5c13b5d3983f1c17e5f88b8ff266"} + + gotCode, err := GetVerificationCode2_3(files, "thisfile.spdx") + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + if wantCode.Value != gotCode.Value { + t.Errorf("expected %v, got %v", wantCode, gotCode) + } +} + +func TestPackage2_3GetVerificationCodeFailsIfNilFileInSlice(t *testing.T) { + files := []*v2_3.File{ + { + FileName: "file2.txt", + FileSPDXIdentifier: "File0", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + nil, + { + FileName: "file3.txt", + FileSPDXIdentifier: "File2", + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", + }, + }, + }, + } + + _, err := GetVerificationCode2_3(files, "") + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} |