Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ default Vulnerability getByVulnIdAndSource(final String vulnId, final String sou

@SqlQuery(/* language=InjectedFreeMarker */ """
<#-- @ftlvariable name="activeFilter" type="Boolean" -->
<#-- @ftlvariable name="searchText" type="Boolean" -->
<#-- @ftlvariable name="apiOrderByClause" type="String" -->
<#-- @ftlvariable name="apiOffsetLimitClause" type="String" -->
<#-- @ftlvariable name="apiProjectAclCondition" type="String" -->
Expand Down Expand Up @@ -200,6 +201,13 @@ WHERE EXISTS(
SELECT 1
FROM "CTE_AFFECTED_COMPONENTS"
WHERE "PROJECT_ID" = "PROJECT"."ID")
<#if searchText>
AND (
LOWER("PROJECT"."NAME") LIKE ('%' || LOWER(:searchText) || '%')
OR LOWER(COALESCE("PROJECT"."VERSION", '')) LIKE ('%' || LOWER(:searchText) || '%')
OR CAST("PROJECT"."UUID" AS TEXT) LIKE ('%' || :searchText || '%')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is searching by UUID a requirement? I think no other endpoint uses searchText for UUID columns.

None of these expressions are indexed, so the fewer we can use the better.

)
</#if>
<#if apiOrderByClause??>
${apiOrderByClause}
<#else>
Expand All @@ -217,7 +225,8 @@ WHERE EXISTS(
List<AffectedProjectListRow> getAffectedProjects(
@Bind String source,
@Bind String vulnId,
@Bind Boolean activeFilter);
@Bind Boolean activeFilter,
@Bind @Nullable String searchText);

record AffectedProjectListRow(
UUID uuid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,11 @@ public Response getVulnerabilityByVulnId(
public Response getAffectedProject(@PathParam("source") String source,
@PathParam("vuln") String vuln,
@Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
@QueryParam("excludeInactive") boolean excludeInactive) {
@QueryParam("excludeInactive") boolean excludeInactive,
@Parameter(description = "Optionally filters affected projects by name, version, or UUID", required = false)
@QueryParam("searchText") String searchText) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: searchText is an implicit parameter defined at the framework-level, no need to define an explicit one. You can access it directly via getAlpineRequest().getFilter().

final List<AffectedProjectListRow> affectedProjectRows = withJdbiHandle(getAlpineRequest(), handle ->
handle.attach(VulnerabilityDao.class).getAffectedProjects(source, vuln, excludeInactive ? true : null));
handle.attach(VulnerabilityDao.class).getAffectedProjects(source, vuln, excludeInactive ? true : null, searchText));

final long totalCount = affectedProjectRows.isEmpty() ? 0 : affectedProjectRows.getFirst().totalCount();
final List<AffectedProject> affectedProjects = affectedProjectRows.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,48 @@ public void getAffectedProjectTest() {
""");
}

@Test
public void getAffectedProjectWithSearchTextTest() {
final var sampleData = new SampleData();
final Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v1.getSource() + "/vuln/" + sampleData.v1.getVulnId() + "/projects")
.queryParam("searchText", "Project 1")
.request()
.header(X_API_KEY, apiKey)
.get(Response.class);
Assertions.assertEquals(200, response.getStatus(), 0);
Assertions.assertEquals(String.valueOf(1), response.getHeaderString(TOTAL_COUNT_HEADER));
assertThatJson(getPlainTextBody(response))
.withMatcher("projectUuid", equalTo(sampleData.p1.getUuid().toString()))
.withMatcher("componentUuid", equalTo(sampleData.c1.getUuid().toString()))
.isEqualTo(/* language=JSON */ """
[
{
"active": true,
"affectedComponentUuids": [
"${json-unit.matches:componentUuid}"
],
"dependencyGraphAvailable": false,
"name": "Project 1",
"uuid": "${json-unit.matches:projectUuid}",
"version": null
}
]
""");
}

@Test
public void getAffectedProjectWithSearchTextNoMatchTest() {
final var sampleData = new SampleData();
final Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v1.getSource() + "/vuln/" + sampleData.v1.getVulnId() + "/projects")
.queryParam("searchText", "does-not-exist")
.request()
.header(X_API_KEY, apiKey)
.get(Response.class);
Assertions.assertEquals(200, response.getStatus(), 0);
Assertions.assertEquals("0", response.getHeaderString(TOTAL_COUNT_HEADER));
Assertions.assertEquals("[]", getPlainTextBody(response));
}

@Test
public void getAffectedProjectWithDeletedFindingTest() {
var project = qm.createProject("Project 1", null, null, null, null, null, null, false);
Expand Down
Loading