Skip to content

Commit 3c2fe11

Browse files
committed
fix: filter by criticality and version
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 0ff57dc commit 3c2fe11

3 files changed

Lines changed: 49 additions & 11 deletions

File tree

backend/src/api/public/v1/packages/getPackageAdvisories.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,33 @@ import { getPackagesQx } from '@/db/packagesDb'
88
import { ok } from '@/utils/api'
99
import { validateOrThrow } from '@/utils/validation'
1010

11-
import { purlQuerySchema } from './purl'
11+
import { extractPurlVersion, purlQuerySchema } from './purl'
1212

1313
const DEFAULT_PAGE_SIZE = 20
1414
const MAX_PAGE_SIZE = 100
1515

16+
const SEVERITY_VALUES = ['critical', 'high', 'moderate', 'low'] as const
17+
1618
const querySchema = purlQuerySchema.extend({
1719
page: z.coerce.number().int().min(1).default(1),
1820
pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE),
21+
severity: z
22+
.preprocess(
23+
(v) => {
24+
if (!v) return undefined
25+
const vals = Array.isArray(v) ? v : [v]
26+
return vals.flatMap((s: unknown) => String(s).split(','))
27+
},
28+
z.array(z.enum(SEVERITY_VALUES)).optional(),
29+
)
30+
.optional(),
1931
})
2032

2133
export async function getPackageAdvisories(req: Request, res: Response): Promise<void> {
22-
const { purl, page, pageSize } = validateOrThrow(querySchema, req.query)
34+
const rawPurl = typeof req.query.purl === 'string' ? req.query.purl : ''
35+
const version = extractPurlVersion(rawPurl)
36+
37+
const { purl, page, pageSize, severity } = validateOrThrow(querySchema, req.query)
2338

2439
const qx = await getPackagesQx()
2540
const pkg = await getPackageDetailByPurl(qx, purl)
@@ -28,7 +43,12 @@ export async function getPackageAdvisories(req: Request, res: Response): Promise
2843
throw new NotFoundError()
2944
}
3045

31-
const { rows, total } = await getAdvisoriesByPackageId(qx, pkg.id, { page, pageSize })
46+
const { rows, total } = await getAdvisoriesByPackageId(qx, pkg.id, {
47+
page,
48+
pageSize,
49+
version: version ?? undefined,
50+
severities: severity,
51+
})
3252

3353
ok(res, {
3454
page,

backend/src/api/public/v1/packages/purl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export function normalizePurl(purl: string): string {
2121
return withoutVersion.replace(/@/g, '%40')
2222
}
2323

24+
export function extractPurlVersion(purl: string): string | null {
25+
const withoutQualifiers = purl.replace(/[?#].*$/, '')
26+
const match = withoutQualifiers.match(/@([^/@]+)$/)
27+
return match ? match[1] : null
28+
}
29+
2430
export const purlFieldSchema = z
2531
.string()
2632
.trim()

services/libs/data-access-layer/src/osspckgs/api.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -775,24 +775,24 @@ export async function listPackagesForScatter(
775775
export async function getAdvisoriesByPackageId(
776776
qx: QueryExecutor,
777777
packageId: string,
778-
opts?: { page: number; pageSize: number },
778+
opts?: { page: number; pageSize: number; version?: string; severities?: string[] },
779779
): Promise<{ rows: AdvisoryRow[]; total: number }> {
780780
const cte = `
781781
WITH advisory_data AS (
782782
SELECT
783783
a.osv_id AS "osvId",
784784
LOWER(a.severity) AS severity,
785785
CASE
786-
WHEN p.latest_version IS NULL THEN NULL
786+
WHEN COALESCE($(version), p.latest_version) IS NULL THEN NULL
787787
WHEN COUNT(ar.id) = 0 THEN NULL
788788
-- TODO: text comparison is lexicographic, not semver — '1.9.0' >= '1.10.0' is TRUE here.
789789
-- Replace with a proper semver comparison function when one is available in the DB.
790790
WHEN BOOL_AND(
791791
CASE
792792
WHEN ar.fixed_version IS NULL AND ar.last_affected IS NULL THEN FALSE
793-
WHEN ar.fixed_version IS NOT NULL AND p.latest_version >= ar.fixed_version THEN TRUE
793+
WHEN ar.fixed_version IS NOT NULL AND COALESCE($(version), p.latest_version) >= ar.fixed_version THEN TRUE
794794
WHEN ar.fixed_version IS NOT NULL THEN FALSE
795-
WHEN ar.last_affected IS NOT NULL AND p.latest_version > ar.last_affected THEN TRUE
795+
WHEN ar.last_affected IS NOT NULL AND COALESCE($(version), p.latest_version) > ar.last_affected THEN TRUE
796796
ELSE FALSE
797797
END
798798
) THEN 'patched'
@@ -807,25 +807,37 @@ export async function getAdvisoriesByPackageId(
807807
)
808808
`
809809

810+
const severityClause = opts?.severities?.length
811+
? `WHERE severity = ANY($(severities)::text[])`
812+
: ''
810813
const paginationClause = opts ? `LIMIT $(limit) OFFSET $(offset)` : ''
814+
const params = {
815+
packageId,
816+
version: opts?.version ?? null,
817+
severities: opts?.severities ?? null,
818+
limit: opts?.pageSize,
819+
offset: opts ? (opts.page - 1) * opts.pageSize : 0,
820+
}
811821

812822
const rows = (await qx.select(
813823
`${cte} SELECT * FROM advisory_data
824+
${severityClause}
814825
ORDER BY
815826
CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'moderate' THEN 3 WHEN 'low' THEN 4 ELSE 5 END,
816827
CASE resolution WHEN 'open' THEN 1 WHEN 'patched' THEN 2 ELSE 3 END,
817828
"osvId"
818829
${paginationClause}`,
819-
{ packageId, limit: opts?.pageSize, offset: opts ? (opts.page - 1) * opts.pageSize : 0 },
830+
params,
820831
)) as AdvisoryRow[]
821832

822833
if (!opts) {
823834
return { rows, total: rows.length }
824835
}
825836

826-
const countResult = (await qx.selectOne(`${cte} SELECT COUNT(*) AS total FROM advisory_data`, {
827-
packageId,
828-
})) as { total: string }
837+
const countResult = (await qx.selectOne(
838+
`${cte} SELECT COUNT(*) AS total FROM advisory_data ${severityClause}`,
839+
params,
840+
)) as { total: string }
829841

830842
return { rows, total: Number(countResult.total) }
831843
}

0 commit comments

Comments
 (0)