-
Notifications
You must be signed in to change notification settings - Fork 29
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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) | ||
} | ||
} |
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/" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ const ( | |
customParserTypeJavaWebAppArchive = "war" | ||
customParserGitHubActions = "github-actions" | ||
customParserTerraform = "terraform" | ||
customParserComposer = "composer" | ||
) | ||
|
||
var ( | ||
|
@@ -51,6 +52,7 @@ var supportedEcosystems map[string]bool = map[string]bool{ | |
models.EcosystemSpdxSBOM: true, | ||
models.EcosystemGitHubActions: true, | ||
models.EcosystemTerraform: true, | ||
models.EcosystemPHPComposer: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not required. We should just use the |
||
} | ||
|
||
// TODO: Migrate these to graph parser | ||
|
@@ -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 | ||
|
@@ -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) { | ||
|
@@ -259,6 +263,8 @@ func (pw *parserWrapper) Ecosystem() string { | |
return models.EcosystemGitHubActions | ||
case customParserTerraform: | ||
return models.EcosystemTerraform | ||
case customParserComposer: | ||
return models.EcosystemPHPComposer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 "" | ||
|
There was a problem hiding this comment.
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