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
152 changes: 77 additions & 75 deletions api/src/main/openapi/paths/components.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,90 +56,94 @@ get:
operationId: listComponents
summary: List all components
description: |-
Retrieves a list of all components that have the specified component identity. This resource accepts coordinates (group, name, version) , `purl`, `cpe`, `swidTagId`, `project_uuid`, or `hash`.

Retrieves a list of all components matching the provided filter criteria.

Text filters are case-insensitive.

### Sortable fields

Sorting is supported for the following fields:

* `name`
* `version`
* `group`
* `purl`
* `cpe`
* `swid_tag_id`
* `last_inherited_risk_score`

Requires permission <strong>VIEW_PORTFOLIO</strong>
tags:
- Components
- Components
parameters:
- name: project_uuid
in: query
description: The UUID of the project to retrieve components for
schema:
type: string
format: uuid
- name: group
in: query
description: The group of the component
schema:
type: string
- name: name
in: query
description: The name of the component
schema:
type: string
- name: version
in: query
description: The version of the component
schema:
type: string
- name: purl
in: query
description: The PURL of the component
schema:
type: string
- name: cpe
in: query
description: The CPE of the component
schema:
type: string
- name: swid_tag_id
in: query
description: The SWID Tag ID of the component
schema:
type: string
- name: hash_type
in: query
description: The MD5, SHA1, SHA_256, SHA_384, SHA_512, SHA3_256, SHA3_384, SHA3_512, BLAKE2B_256, BLAKE2B_384, BLAKE2B_512, or BLAKE3 hash type of the component
schema:
type: string
enum:
- MD5
- SHA1
- SHA_256
- SHA_384
- SHA_512
- SHA3_256
- SHA3_384
- SHA3_512
- BLAKE2B_256
- BLAKE2B_384
- BLAKE2B_512
- BLAKE3
- name: hash
in: query
description: The hash value of the component
schema:
type: string
- $ref: "../components/parameters/pagination-limit.yaml"
- $ref: "../components/parameters/page-token.yaml"
- $ref: "../components/parameters/sort-direction.yaml"
- $ref: "../components/parameters/sort-by.yaml"
- name: group_contains
in: query
description: Filter by group (substring match)
schema:
type: string
- name: name_contains
in: query
description: Filter by name (substring match)
schema:
type: string
- name: version_contains
in: query
description: Filter by version (substring match)
schema:
type: string
- name: purl_prefix
in: query
description: |-
Filter by PURL (prefix match).

Must be a valid PURL, with at least `pkg:<ecosystem>/<name>` populated.
schema:
type: string
- name: cpe
in: query
description: |-
Filter by CPE (exact match).

Must be a valid CPE.
schema:
type: string
- name: swid_tag_id_contains
in: query
description: Filter by SWID Tag ID (substring match)
schema:
type: string
- name: hash_type
in: query
description: The hash type to filter by
schema:
type: string
enum:
- MD5
- SHA1
- SHA_256
- SHA_384
- SHA_512
- SHA3_256
- SHA3_384
- SHA3_512
- BLAKE2B_256
- BLAKE2B_384
- BLAKE2B_512
- BLAKE3
- name: hash
in: query
description: |-
Filter by hash value (exact match).

Requires `hash_type` to be set.
schema:
type: string
- $ref: "../components/parameters/pagination-limit.yaml"
- $ref: "../components/parameters/page-token.yaml"
- $ref: "../components/parameters/sort-direction.yaml"
- $ref: "../components/parameters/sort-by.yaml"
responses:
"200":
description: A list of all components for a given identity
description: A list of components matching the provided filters
content:
application/json:
schema:
Expand All @@ -150,13 +154,11 @@ get:
application/problem+json:
schema:
anyOf:
- $ref: "../components/schemas/json-schema-validation-problem-details.yaml"
- $ref: "../components/schemas/problem-details.yaml"
- $ref: "../components/schemas/json-schema-validation-problem-details.yaml"
- $ref: "../components/schemas/problem-details.yaml"
"401":
$ref: "../components/responses/generic-unauthorized-error.yaml"
"403":
$ref: "../components/responses/generic-forbidden-error.yaml"
"404":
$ref: "../components/responses/generic-not-found-error.yaml"
default:
$ref: "../components/responses/generic-error.yaml"
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ default Page<Component> listComponents(
queryParams.put("componentPurl", componentPurl);
}
if (componentCpe != null) {
whereConditions.add("LOWER(\"C\".\"CPE\") LIKE ('%' || LOWER(:componentCpe) || '%')");
whereConditions.add("LOWER(\"C\".\"CPE\") = LOWER(:componentCpe)");
queryParams.put("componentCpe", componentCpe);
}
if (componentSwidTagId != null) {
Expand Down Expand Up @@ -298,7 +298,6 @@ default Page<Component> listComponents(
case "group" -> SortBy.GROUP;
case "purl" -> SortBy.PURL;
case "cpe" -> SortBy.CPE;
case "swid_tag_id" -> SortBy.SWIDTAGID;
case "last_inherited_risk_score" -> SortBy.LAST_RISKSCORE;
case null, default -> null;
};
Expand All @@ -321,7 +320,6 @@ default Page<Component> listComponents(
case SortBy.GROUP -> lastRow.getGroup();
case SortBy.PURL -> lastRow.getPurl();
case SortBy.CPE -> lastRow.getCpe();
case SortBy.SWIDTAGID -> lastRow.getSwidTagId();
case SortBy.LAST_RISKSCORE -> lastRow.getLastInheritedRiskScore();
case null -> lastRow.getName();
};
Expand Down Expand Up @@ -450,7 +448,6 @@ enum SortBy {
GROUP,
PURL,
CPE,
SWIDTAGID,
LAST_RISKSCORE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import org.dependencytrack.model.Project;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.persistence.jdbi.ComponentDao;
import org.dependencytrack.persistence.jdbi.ProjectDao;
import org.dependencytrack.resources.AbstractApiResource;
import org.dependencytrack.util.InternalComponentIdentifier;
import org.dependencytrack.util.PurlUtil;
Expand Down Expand Up @@ -109,23 +108,15 @@

@Override
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response listComponents(UUID projectUuid, String group, String name, String version, String purl, String cpe,
String swidTagId, String hashType, String hash, Integer limit, String pageToken, SortDirection sortDirection, String sortBy) {
public Response listComponents(String groupContains, String nameContains, String versionContains, String purlPrefix, String cpe,

Check warning on line 111 in apiserver/src/main/java/org/dependencytrack/resources/v2/ComponentsResource.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

apiserver/src/main/java/org/dependencytrack/resources/v2/ComponentsResource.java#L111

Avoid long parameter lists.

Check warning on line 111 in apiserver/src/main/java/org/dependencytrack/resources/v2/ComponentsResource.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

apiserver/src/main/java/org/dependencytrack/resources/v2/ComponentsResource.java#L111

The method 'listComponents(String, String, String, String, String, String, String, String, Integer, String, SortDirection, String)' has an NPath complexity of 216, current threshold is 200
String swidTagIdContains, String hashType, String hash, Integer limit, String pageToken, SortDirection sortDirection, String sortBy) {
return inJdbiTransaction(getAlpineRequest(), handle -> {
Long projectId = null;
if (projectUuid != null) {
projectId = handle.attach(ProjectDao.class).getProjectId(projectUuid);
if (projectId == null) {
throw new NotFoundException();
}
requireProjectAccess(handle, projectUuid);
}
PackageURL packageURL = null;
if (purl != null) {
if (purlPrefix != null) {
try {
packageURL = new PackageURL(StringUtils.trimToNull(purl));
packageURL = new PackageURL(StringUtils.trimToNull(purlPrefix));
} catch (MalformedPackageURLException e) {
throw new BadRequestException("Invalid package URL: %s".formatted(purl));
throw new BadRequestException("Invalid package URL: %s".formatted(purlPrefix));
}
Comment thread
nscuro marked this conversation as resolved.
}
if (cpe != null) {
Expand All @@ -144,9 +135,9 @@
}
}
Comment thread
nscuro marked this conversation as resolved.
final Page<Component> componentsPage = handle.attach(ComponentDao.class)
.listComponents(projectId, true, packageURL != null ? packageURL.canonicalize().toLowerCase() : null, StringUtils.trimToNull(cpe),
StringUtils.trimToNull(swidTagId), StringUtils.trimToNull(group), StringUtils.trimToNull(name),
StringUtils.trimToNull(version), hashTypeEnum, StringUtils.trimToNull(hash), limit, pageToken, sortBy, mapSortDirection(sortDirection));
.listComponents(null, true, packageURL != null ? packageURL.canonicalize().toLowerCase() : null, StringUtils.trimToNull(cpe),
StringUtils.trimToNull(swidTagIdContains), StringUtils.trimToNull(groupContains), StringUtils.trimToNull(nameContains),
StringUtils.trimToNull(versionContains), hashTypeEnum, StringUtils.trimToNull(hash), limit, pageToken, sortBy, mapSortDirection(sortDirection));

Comment thread
nscuro marked this conversation as resolved.
final var response = ListComponentsResponse.builder()
.items(componentsPage.items().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.util.UUID;

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -302,9 +300,9 @@ public void listComponentsSortingTest() {
public void listComponentsWithCoordinatesTest() {
prepareComponents();
Response response = jersey.target("/components")
.queryParam("group", "B")
.queryParam("name", "B")
.queryParam("version", "versionB")
.queryParam("group_contains", "B")
.queryParam("name_contains", "B")
.queryParam("version_contains", "versionB")
.queryParam("limit", 2)
.request()
.header(X_API_KEY, apiKey)
Expand Down Expand Up @@ -341,7 +339,7 @@ public void listComponentsWithCoordinatesTest() {
public void listComponentsWithPurlTest() {
prepareComponents();
Response response = jersey.target("/components")
.queryParam("purl", "pkg:maven/groupB/nameB@versionB")
.queryParam("purl_prefix", "pkg:maven/groupB/nameB@versionB")
.queryParam("limit", 2)
.request()
.header(X_API_KEY, apiKey)
Expand Down Expand Up @@ -423,63 +421,13 @@ public void listComponentsWithCpeTest() {
""");
}

@Test
public void listComponentsWithProjectTest() {
prepareComponents();
Response response = jersey.target("/components")
.queryParam("project_uuid", qm.getProject("projectA", "1.0").getUuid())
.queryParam("limit", 2)
.request()
.header(X_API_KEY, apiKey)
.get();
assertThat(response.getStatus()).isEqualTo(200);
final JsonObject responseJson = parseJsonObject(response);
assertThatJson(responseJson.toString()).isEqualTo(/* language=JSON */ """
{
"items" : [ {
"name": "nameA",
"version": "versionA",
"group": "groupA",
"cpe": "cpe:2.3:a:groupA:nameA:versionA:*:*:*:*:*:*:*",
"purl":"pkg:maven/groupA/nameA@versionA?foo=bar",
"internal": false,
"uuid": "${json-unit.any-string}",
"project": {
"name": "projectA",
"version": "1.0",
"uuid": "${json-unit.any-string}"
}
}
],
"total": {
"count": 1,
"type": "EXACT"
}
}
""");
}

@Test
public void listComponentsWithProjectWhenProjectDoesNotExistTest() {
prepareComponents();
Response response = jersey.target("/components")
.queryParam("project_uuid", UUID.randomUUID())
.queryParam("limit", 2)
.request()
.header(X_API_KEY, apiKey)
.get();
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_NOT_FOUND);
assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isNull();
assertThat(getPlainTextBody(response)).contains("Not Found");
}

@Test
public void listComponentsAclTest() {
enablePortfolioAccessControl();
initializeWithPermissions(Permissions.PORTFOLIO_MANAGEMENT);
prepareComponents();
Response response = jersey.target("/components")
.queryParam("name", "name")
.queryParam("name_contains", "name")
.queryParam("limit", 2)
.request()
.header(X_API_KEY, apiKey)
Expand Down
Loading