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 php lockfile parser #310

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
EcosystemTerraform = "Terraform"
EcosystemTerraformModule = "TerraformModule"
EcosystemTerraformProvider = "TerraformProvider"
EcosystemPHPComposer = "Composer"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Ecosystem for PHP Composer is actually called Packagist
https://osv.dev/list?ecosystem=Packagist

)

type ManifestSourceType string
Expand Down
45 changes: 45 additions & 0 deletions pkg/parser/composer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package parser

import (
"encoding/json"
"fmt"
"os"

"github.com/safedep/vet/pkg/models"
)

type ComposerJSON struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment with reference to Composer JSON specification from official source? We need to see if we need any other information like dependency relationships for building dependency graph.

Require map[string]string `json:"require"`
}

func parseComposerJSON(path string, config *ParserConfig) (*models.PackageManifest, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open composer.json: %v", err)
}
defer file.Close()

var composerData ComposerJSON
err = json.NewDecoder(file).Decode(&composerData)
if err != nil {
return nil, fmt.Errorf("failed to parse composer.json: %v", err)
}

manifest := models.NewPackageManifestFromLocal(path, models.EcosystemPHPComposer)
for packageName, version := range composerData.Require {
if packageName == "php" {
// Skip the PHP version specification itself
continue
}

pkgdetails := models.NewPackageDetail(models.EcosystemPHPComposer, packageName, version)
packageModel := models.Package{
PackageDetails: pkgdetails,
Depth: 0,
}

manifest.AddPackage(&packageModel)
}

return manifest, nil
}
71 changes: 71 additions & 0 deletions pkg/parser/composer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package parser

import (
"testing"

"github.com/safedep/vet/pkg/models"
"github.com/stretchr/testify/assert"
)

func findPackageInComposerManifest(manifest *models.PackageManifest, name, version string) *models.Package {
for _, pkg := range manifest.GetPackages() {
if pkg.GetName() == name && (version == "" || pkg.GetVersion() == version) {
return pkg
}
}
return nil
}

func TestComposerJSONParserBasic(t *testing.T) {
pm, err := parseComposerJSON("./fixtures/composer.json", defaultParserConfigForTest)
assert.Nil(t, err)

assert.NotNil(t, pm)
assert.NotEmpty(t, pm.GetPackages())
}

func TestComposerJSONParserSpecificPackage(t *testing.T) {
pm, err := parseComposerJSON("./fixtures/composer.json", defaultParserConfigForTest)
assert.Nil(t, err)

monolog := findPackageInComposerManifest(pm, "monolog/monolog", "^2.0")
assert.NotNil(t, monolog)
assert.Equal(t, "^2.0", monolog.GetVersion())
}

func TestComposerJSONParserAllPackages(t *testing.T) {
pm, err := parseComposerJSON("./fixtures/composer.json", defaultParserConfigForTest)
assert.Nil(t, err)

expectedPackages := []string{
"monolog/monolog",
"guzzlehttp/guzzle",
"symfony/console",
}

for _, packageName := range expectedPackages {
pkg := findPackageInComposerManifest(pm, packageName, "")
assert.NotNil(t, pkg, "Package %s should be present", packageName)
assert.Equal(t, models.EcosystemPHPComposer, pm.Ecosystem)
}
}

func TestComposerJSONParserPackageVersions(t *testing.T) {
pm, err := parseComposerJSON("./fixtures/composer.json", defaultParserConfigForTest)
assert.Nil(t, err)

packages := []struct {
name string
version string
}{
{"monolog/monolog", "^2.0"},
{"guzzlehttp/guzzle", "^7.0"},
{"symfony/console", "^5.4"},
}

for _, pkg := range packages {
foundPkg := findPackageInComposerManifest(pm, pkg.name, pkg.version)
assert.NotNil(t, foundPkg, "Package %s@%s should be present", pkg.name, pkg.version)
assert.Equal(t, pkg.version, foundPkg.GetVersion(), "Package %s should have version %s", pkg.name, pkg.version)
}
}
18 changes: 18 additions & 0 deletions pkg/parser/fixtures/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "sample/project",
"description": "A sample PHP project to test the parser.",
"require": {
"php": "^7.4 || ^8.0",
"monolog/monolog": "^2.0",
"guzzlehttp/guzzle": "^7.0",
"symfony/console": "^5.4"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"Sample\\Project\\": "src/"
}
}
}
6 changes: 6 additions & 0 deletions pkg/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
customParserTypeJavaWebAppArchive = "war"
customParserGitHubActions = "github-actions"
customParserTerraform = "terraform"
customParserComposer = "composer"
)

var (
Expand Down Expand Up @@ -51,6 +52,7 @@ var supportedEcosystems map[string]bool = map[string]bool{
models.EcosystemSpdxSBOM: true,
models.EcosystemGitHubActions: true,
models.EcosystemTerraform: true,
models.EcosystemPHPComposer: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not required. We should just use the Packagist ecosystem

}

// TODO: Migrate these to graph parser
Expand Down Expand Up @@ -92,6 +94,7 @@ var dependencyGraphParsers map[string]dependencyGraphParser = map[string]depende
customParserTypeJavaWebAppArchive: parseJavaArchiveAsGraph,
customParserGitHubActions: parseGithubActionWorkflowAsGraph,
customParserTerraform: parseTerraformLockfile,
customParserComposer: parseComposerJSON,
}

// Maintain a map of extension to lockfileAs
Expand All @@ -106,6 +109,7 @@ var lockfileAsMapByExtension map[string]string = map[string]string{
// reference to this map to resolve the lockfileAs from base filename
var lockfileAsMapByPath map[string]string = map[string]string{
".terraform.lock.hcl": customParserTerraform,
"composer.json": customParserComposer,
}

func FindLockFileAsByExtension(extension string) (string, error) {
Expand Down Expand Up @@ -259,6 +263,8 @@ func (pw *parserWrapper) Ecosystem() string {
return models.EcosystemGitHubActions
case customParserTerraform:
return models.EcosystemTerraform
case customParserComposer:
return models.EcosystemPHPComposer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be packagist ecosystem

default:
logger.Debugf("Unsupported lockfile-as %s", pw.parseAs)
return ""
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestListParser(t *testing.T) {
parsers := List(false)
assert.Equal(t, 19, len(parsers))
assert.Equal(t, 20, len(parsers))
}

func TestInvalidEcosystemMapping(t *testing.T) {
Expand Down
Loading