Skip to content
Merged
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
5 changes: 0 additions & 5 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ linters:
linters:
- errcheck
- gosec
# PickClosestUrls is intentionally complex — the semver selection logic doesn't split cleanly.
- path: pkg/models/all_urls\.go
linters:
- gocognit
text: PickClosestUrls
# sqliteSetup is a dormant test helper kept for future SQLite-backed test paths.
- path: pkg/models/common\.go
linters:
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## [Unreleased]

## [0.11.0] - 2026-06-02
### Added
- Integrated `go-component-helper` (`componenthelper.GetComponentsVersion`) as the single source of truth for resolving and validating version requirements, querying the same knowledge base (`all_urls`/`versions`/`mines`)
- Added a source-code purl fallback: when a cryptography or library lookup returns no info, the query is retried against the component's linked source purl; recovered results are flagged with a `WARNING` info_code and the message `Showing results from the source code purl (<source purl>)`
- Added `github.com/scanoss/go-component-helper` and `github.com/scanoss/go-models` dependencies

### Changed
- Centralized requirement resolution in `pkg/usecase/component_resolver.go`, replacing the divergent per-endpoint validation/resolution logic
- Range endpoints now return `VERSION_NOT_FOUND` (instead of `COMPONENT_NOT_FOUND`) when the component exists but no known version satisfies the requirement

### Removed
- Removed redundant local resolution helpers now provided by the component helper: `parseAndValidateComponent`, `utils.IsValidRequirement`, `processPurlVersion`, `models.PickClosestUrls`, `models.GetUrlsByPurlNameTypeInRange`

### Fixed
- Fixed `golangci-lint` issues (line length, unused parameter and always-nil return) and removed a stale lint exclusion


## [0.10.0] - 2026-04-20
### Changed
- Replaced `error_message`/`error_code` fields with `info_message`/`info_code` across response builders and domain structs (algorithms, algorithms in range, encryption hints, hints in range, versions in range)
Expand Down Expand Up @@ -115,6 +132,7 @@
- Remove from list those versions that do not contain detections
- Detailed response status message.

[0.11.0]: https://github.com/scanoss/cryptography/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/scanoss/cryptography/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/scanoss/cryptography/compare/v0.8.1...v0.9.0
[0.8.1]: https://github.com/scanoss/cryptography/compare/v0.8.0...v0.8.1
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ module scanoss.com/cryptography
go 1.25.0

require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/semver/v3 v3.5.0
github.com/golobby/config/v3 v3.4.2
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.12.3
github.com/scanoss/go-component-helper v0.7.0
github.com/scanoss/go-grpc-helper v0.15.1
github.com/scanoss/go-purl-helper v0.3.0
github.com/scanoss/papi v0.41.0
github.com/scanoss/zap-logging-helper v0.4.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/metric v1.43.0
go.uber.org/zap v1.27.1
go.uber.org/zap v1.28.0
google.golang.org/grpc v1.80.0
modernc.org/sqlite v1.48.2
)
Expand Down Expand Up @@ -55,6 +56,7 @@ require (
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/scanoss/go-models v0.10.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.50.0 // indirect
Expand Down
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
Expand Down Expand Up @@ -626,8 +626,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/scanoss/go-component-helper v0.7.0 h1:NB0gJdzYQ8kg/UEifAkVnXzdrVT1IaoyipRaYopdsj0=
github.com/scanoss/go-component-helper v0.7.0/go.mod h1:rvdEl0tRWndm7LeaN1m7iD4QoIryI70fIfzdS+EHylY=
github.com/scanoss/go-grpc-helper v0.15.1 h1:tiI7JXVj2LYpktkYVqCcUd6XyMi6aVcAKRecNR5QGCA=
github.com/scanoss/go-grpc-helper v0.15.1/go.mod h1:UjrF9hewgZxstUC3+F0PJnuOXH0bChmsbcJp7e7OsH4=
github.com/scanoss/go-models v0.10.0 h1:x+9XirwTpbLQBj3X9J+D8Lauu0oYB9bxjLafkEMpFtI=
github.com/scanoss/go-models v0.10.0/go.mod h1:vRSlL4kxtprSwTIAARXVcVZ820tCQfkx6yn6038oY6A=
github.com/scanoss/go-purl-helper v0.3.0 h1:zH5rcYbmYTvKms2oWrYV+8rWZ2ElLgDIOy2jZ9XhAg0=
github.com/scanoss/go-purl-helper v0.3.0/go.mod h1:3CFUM/OuUp9Q58IF/yGkQhr+G4x6hJNmF8N1f0W82C4=
github.com/scanoss/ipfilter/v2 v2.0.2 h1:GaB9i8kVJg9JQZm5XGStYkEpiaCVdsrj7ezI2wV/oh8=
Expand Down Expand Up @@ -704,8 +708,10 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down
3 changes: 0 additions & 3 deletions pkg/handlers/cryptography_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ func ConvertPurlRequestToComponentDTO(s *zap.SugaredLogger, request *common.Purl
func buildComponentDTO(purl string, requirement string) dtos.ComponentDTO {
p := purl
req := requirement
if requirement != "" {
req = requirement
}
purlParts := strings.Split(purl, "@")
if len(purlParts) > 1 {
p = purlParts[0]
Expand Down
137 changes: 0 additions & 137 deletions pkg/models/all_urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,72 +173,6 @@ func (m *AllUrlsModel) GetUrlsByPurlNameTypeVersion(ctx context.Context, s *zap.
return pickOneURL(s, allUrls, purlName, purlType, "")
}

func (m *AllUrlsModel) GetUrlsByPurlNameTypeInRange(ctx context.Context, s *zap.SugaredLogger, purlName, purlType, purlRange string) ([]AllURL, error) {
if len(purlName) == 0 {
s.Infof("Please specify a valid Purl Name to query")
return []AllURL{}, errors.New("please specify a valid Purl Name to query")
}
if len(purlType) == 0 {
s.Infof("Please specify a valid Purl Type to query")
return []AllURL{}, errors.New("please specify a valid Purl Type to query")
}
if len(purlRange) == 0 {
s.Infof("Please specify a valid Purl Version range to query")
return []AllURL{}, errors.New("please specify a valid Purl Version to query")
}
var allUrls []AllURL
var filteredUrls []AllURL
err := m.db.SelectContext(ctx, &allUrls,
"SELECT package_hash AS url_hash, component, v.version_name AS version, v.semver AS semver, "+
"purl_name, mine_id FROM all_urls u "+
"LEFT JOIN mines m ON u.mine_id = m.id "+
"LEFT JOIN versions v ON u.version_id = v.id "+
"WHERE m.purl_type = $1 AND u.purl_name = $2 "+
"AND package_hash!='404' "+
"ORDER BY date DESC;",
purlType, purlName)
if err != nil {
s.Infof("Failed to query all urls table for %v - %v: %v", purlType, purlName, err)
return []AllURL{}, fmt.Errorf("failed to query the all urls table: %v", err)
}
rangeSpec, err := semver.NewConstraint(purlRange)
if err != nil {
return []AllURL{}, fmt.Errorf("failed to analyze range: %v", err)
}
// Track versions without semver for summary reporting
woSemver := []string{}

// Iterate through all URLs to filter versions within the specified range
for _, u := range allUrls {
// Skip entries without semver and collect them for reporting
if u.SemVer == "" {
woSemver = append(woSemver, u.Version)
continue
}

// Parse the semver version string
version, err := semver.NewVersion(u.SemVer)
if err != nil {
// Skip invalid semver versions
continue
}

// Check if the version satisfies the range constraint
if rangeSpec.Check(version) {
filteredUrls = append(filteredUrls, u)
}
}

// If all/most versions lack semver and no filtered results exist,
// add this purl to the summary for reporting purposes
if len(woSemver) >= len(allUrls) && len(allUrls) > 0 && len(filteredUrls) == 0 {
return []AllURL{}, errors.New("URLS has not valid semver")
}
s.Debugf("Found %d results for %v, %v.", len(filteredUrls), purlType, purlName)
// Pick one URL to return
return filteredUrls, nil
}

// pickOneURL takes the potential matching component/versions and selects the most appropriate one
// obsolete in this application.
func pickOneURL(s *zap.SugaredLogger, allUrls []AllURL, purlName, purlType, purlReq string) (AllURL, error) {
Expand Down Expand Up @@ -308,74 +242,3 @@ func pickOneURL(s *zap.SugaredLogger, allUrls []AllURL, purlName, purlType, purl
s.Debugf("Selected version: %#v", url)
return url, nil // Return the best component match
}

// PickClosestUrls nolint: gocognit.
func PickClosestUrls(s *zap.SugaredLogger, allUrls []AllURL, purlName, purlType, purlReq string) ([]AllURL, error) {
if len(allUrls) == 0 {
s.Infof("No component match (in urls) found for %v, %v", purlName, purlType)
return []AllURL{}, nil
}
var c *semver.Constraints
var urlMap = make(map[*semver.Version][]AllURL)

if len(purlReq) > 0 {
s.Debugf("Building version constraint for %v: %v", purlName, purlReq)
var err error
c, err = semver.NewConstraint(purlReq)
if err != nil {
s.Warnf("Encountered an issue parsing version constraint string '%v' (%v,%v): %v", purlReq, purlName, purlType, err)
}
}
s.Debugf("Checking versions...")
for _, url := range allUrls {
if len(url.SemVer) > 0 || len(url.Version) > 0 {
v, err := semver.NewVersion(url.Version)
if err != nil && len(url.SemVer) > 0 {
s.Debugf("Failed to parse SemVer: '%v'. Trying Version instead: %v (%v)", url.Version, url.SemVer, err)
v, err = semver.NewVersion(url.SemVer) // Semver failed, try the normal version
}
if err != nil {
s.Warnf("Encountered an issue parsing version string '%v' (%v) for %v: %v. Using v0.0.0", url.Version, url.SemVer, url, err)
v, err = semver.NewVersion("v0.0.0") // Semver failed, just use a standard version zero (for now)
}
if err == nil {
if c == nil || c.Check(v) {
found := false
for k := range urlMap {
if k.Equal(v) {
urlMap[k] = append(urlMap[k], url)
found = true
break
}
}
if !found {
urlMap[v] = append(urlMap[v], url)
}
}
}
} else {
s.Warnf("Skipping match as it doesn't have a version: %#v", url)
}
}
if len(urlMap) == 0 { // TODO should we return the latest version anyway?
s.Warnf("No component match found for %v, %v after filter %v", purlName, purlType, purlReq)
return []AllURL{}, nil
}
var versions = make([]*semver.Version, len(urlMap))
var vi = 0
for version := range urlMap { // Save the list of versions so they can be sorted
versions[vi] = version
vi++
}
sort.Sort(semver.Collection(versions))
version := versions[len(versions)-1] // Get the latest (acceptable) URL version

url, ok := urlMap[version] // Retrieve the latest accepted URL version
if !ok {
s.Errorf("Problem retrieving URL data for %v (%v, %v)", version, purlName, purlType)
return []AllURL{}, fmt.Errorf("failed to retrieve specific URL version: %v", version)
}

s.Debugf("Selected version: %#v", url)
return url, nil // Return the closest URLs
}
78 changes: 0 additions & 78 deletions pkg/models/all_urls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,53 +132,6 @@ func TestAllUrlsSearchVersionRequirement(t *testing.T) {
}
}

func TestAllUrlsSearchVersionRange(t *testing.T) {
defer testutils.SetupTestRulesetsDir(t)()

err := zlog.NewSugaredDevLogger()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a sugared logger", err)
}
defer zlog.SyncZap()
ctx := ctxzap.ToContext(context.Background(), zlog.L)
s := ctxzap.Extract(ctx).Sugar()
db := sqliteSetup(t) // Setup SQL Lite DB
defer CloseDB(db)

err = LoadTestSQLData(db, ctx)
if err != nil {
t.Fatalf("failed to load SQL test data: %v", err)
}
myConfig, err := myconfig.NewServerConfig(nil)
if err != nil {
t.Fatalf("failed to load Config: %v", err)
}
myConfig.Database.Trace = true
allUrlsModel := NewAllURLModel(db)
allUrls, err := allUrlsModel.GetUrlsByPurlNameTypeInRange(ctx, s, "scanoss/engine", "github", ">2.0")
if err != nil {
t.Errorf("all_urls.GetUrlsByPurlNameTypeInRange() error = %v", err)
}
if len(allUrls) == 0 {
t.Errorf("all_urls.GetUrlsByPurlString() No URLs returned from query")
}
fmt.Printf("All Urls Version: %#v\n", allUrls)

_, err = allUrlsModel.GetUrlsByPurlNameTypeInRange(ctx, s, "scanoss/engine", "github", "")
if err == nil {
t.Errorf("expected error all_urls.GetUrlsByPurlNameTypeInRange() ")
}

_, err = allUrlsModel.GetUrlsByPurlNameTypeInRange(ctx, s, "", "github", ">2.0")
if err == nil {
t.Errorf("Expected all_urls.GetUrlsByPurlNameTypeInRange() error = %v", err)
}
_, err = allUrlsModel.GetUrlsByPurlNameTypeInRange(ctx, s, "scanoss/engine", "", ">2.0")
if err == nil {
t.Errorf("Expected all_urls.GetUrlsByPurlNameTypeInRange() error = %v", err)
}
}

func TestAllUrlsSearchPurlList(t *testing.T) {
defer testutils.SetupTestRulesetsDir(t)()

Expand Down Expand Up @@ -211,37 +164,6 @@ func TestAllUrlsSearchPurlList(t *testing.T) {
}
}

func TestAllUrlsClosestVersionRequirement(t *testing.T) {
defer testutils.SetupTestRulesetsDir(t)()

err := zlog.NewSugaredDevLogger()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a sugared logger", err)
}
defer zlog.SyncZap()
ctx := ctxzap.ToContext(context.Background(), zlog.L)
s := ctxzap.Extract(ctx).Sugar()
db := sqliteSetup(t) // Setup SQL Lite DB
defer CloseDB(db)
err = LoadTestSQLData(db, ctx)
if err != nil {
t.Fatalf("failed to load SQL test data: %v", err)
}
myConfig, err := myconfig.NewServerConfig(nil)
if err != nil {
t.Fatalf("failed to load Config: %v", err)
}
myConfig.Database.Trace = true
// allUrlsModel := NewAllURLModel(db)
allUrls := []AllURL{{URLHash: "0", Component: "engine", PurlName: "scanoss/engine", SemVer: "v1.0", PurlType: "github"},
{URLHash: "1", Component: "engine", PurlName: "scanoss/engine", SemVer: "v1.1", PurlType: "github"},
{URLHash: "2", Component: "engine", PurlName: "scanoss/engine", SemVer: "v1.2", PurlType: "github"},
{URLHash: "3", Component: "engine", PurlName: "scanoss/engine", SemVer: "v1.3", PurlType: "github"},
}
_, err = PickClosestUrls(s, allUrls, "scanoss/engine", "github", "v1.3")
fmt.Printf("%+v", allUrls)
}

func TestAllUrlsSearchNoLicense(t *testing.T) {
defer testutils.SetupTestRulesetsDir(t)()

Expand Down
1 change: 1 addition & 0 deletions pkg/models/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func loadSQLData(db *sqlx.DB, ctx context.Context, filename string) error {
// LoadTestSQLData loads all the required test SQL files.
func LoadTestSQLData(db *sqlx.DB, ctx context.Context) error {
files := []string{"../models/tests/mines.sql", "../models/tests/all_urls.sql", "../models/tests/versions.sql",
"../models/tests/licenses.sql",
"../models/tests/component_crypto.sql", "../models/tests/component_crypto_libraries.sql",
"../models/tests/crypto_libraries.sql"}
return loadTestSQLDataFiles(db, ctx, files)
Expand Down
2 changes: 0 additions & 2 deletions pkg/models/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
// - GetUrlsByPurlList: Batch retrieval of components
// - GetUrlsByPurlNameType: Query by package name and type
// - GetUrlsByPurlNameTypeVersion: Query by specific version
// - GetUrlsByPurlNameTypeInRange: Query versions within semantic version ranges
// - PickClosestUrls: Version resolution and constraint matching
//
// CryptoUsageModel (crypto_usage.go):
// - Manages the 'component_crypto' table storing cryptographic algorithm usage
Expand Down
12 changes: 12 additions & 0 deletions pkg/models/tests/licenses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DROP TABLE IF EXISTS licenses;
CREATE TABLE licenses
(
id integer primary key,
license_name text,
spdx_id text,
is_spdx integer
);

INSERT INTO licenses (id, license_name, spdx_id, is_spdx) values (83, 'GPL-2.0-only', 'GPL-2.0-only', 1);
INSERT INTO licenses (id, license_name, spdx_id, is_spdx) values (2815, 'GPL-2.0-only', 'GPL-2.0-only', 1);
INSERT INTO licenses (id, license_name, spdx_id, is_spdx) values (10684, 'Apache-2.0', 'Apache-2.0', 1);
Loading
Loading