Skip to content

Commit

Permalink
Add REST endpoints to tag and untag policies in bulk (#830)
Browse files Browse the repository at this point in the history
  • Loading branch information
sahibamittal authored Aug 7, 2024
1 parent f060f75 commit f98b6b2
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,14 @@ public List<TagQueryManager.TaggedPolicyRow> getTaggedPolicies(final String tagN
return getTagQueryManager().getTaggedPolicies(tagName);
}

public void tagPolicies(final String tagName, final Collection<String> policyUuids) {
getTagQueryManager().tagPolicies(tagName, policyUuids);
}

public void untagPolicies(final String tagName, final Collection<String> policyUuids) {
getTagQueryManager().untagPolicies(tagName, policyUuids);
}

public PaginatedResult getTagsForPolicy(String policyUuid) {
return getTagQueryManager().getTagsForPolicy(policyUuid);
}
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/org/dependencytrack/persistence/TagQueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public TaggedProjectRow(String uuid, String name, String version, int totalCount

}

/**
* @since 4.12.0
*/
public record TagDeletionCandidateRow(
String name,
long projectCount,
Expand All @@ -166,6 +169,10 @@ public TagDeletionCandidateRow(

}

/**
* @since 4.12.0
*/
@Override
public void deleteTags(final Collection<String> tagNames) {
runInTransaction(() -> {
final Map.Entry<String, Map<String, Object>> projectAclConditionAndParams = getProjectAclSqlCondition();
Expand Down Expand Up @@ -448,6 +455,54 @@ public List<TaggedPolicyRow> getTaggedPolicies(final String tagName) {
}
}

/**
* @since 4.12.0
*/
@Override
public void tagPolicies(final String tagName, final Collection<String> policyUuids) {
runInTransaction(() -> {
final Tag tag = getTagByName(tagName);
if (tag == null) {
throw new NoSuchElementException("A tag with name %s does not exist".formatted(tagName));
}

final Query<Policy> policiesQuery = pm.newQuery(Policy.class);
policiesQuery.setFilter(":uuids.contains(uuid)");
policiesQuery.setParameters(policyUuids);
final List<Policy> policies = executeAndCloseList(policiesQuery);

for (final Policy policy : policies) {
bind(policy, List.of(tag));
}
});
}

/**
* @since 4.12.0
*/
@Override
public void untagPolicies(final String tagName, final Collection<String> policyUuids) {
runInTransaction(() -> {
final Tag tag = getTagByName(tagName);
if (tag == null) {
throw new NoSuchElementException("A tag with name %s does not exist".formatted(tagName));
}

final Query<Policy> policiesQuery = pm.newQuery(Policy.class);
policiesQuery.setFilter(":uuids.contains(uuid)");
policiesQuery.setParameters(policyUuids);
final List<Policy> policies = executeAndCloseList(policiesQuery);

for (final Policy policy : policies) {
if (policy.getTags() == null || policy.getTags().isEmpty()) {
continue;
}

policy.getTags().remove(tag);
}
});
}

@Override
public PaginatedResult getTagsForPolicy(String policyUuid) {

Expand Down
12 changes: 10 additions & 2 deletions src/main/java/org/dependencytrack/resources/v1/PolicyResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,10 @@ public Response removeProjectFromPolicy(
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Adds a tag to a policy",
description = "<p>Requires permission <strong>POLICY_MANAGEMENT</strong> or <strong>POLICY_MANAGEMENT_UPDATE</strong></p>"
description = """
<p><strong>Deprecated</strong>. Use <code>POST /api/v1/tag/{name}/policy</code> instead.</p>
<p>Requires permission <strong>POLICY_MANAGEMENT</strong></p>
"""
)
@ApiResponses(value = {
@ApiResponse(
Expand All @@ -335,6 +338,7 @@ public Response removeProjectFromPolicy(
@ApiResponse(responseCode = "404", description = "The policy or tag could not be found")
})
@PermissionRequired({Permissions.Constants.POLICY_MANAGEMENT, Permissions.Constants.POLICY_MANAGEMENT_UPDATE})
@Deprecated(forRemoval = true)
public Response addTagToPolicy(
@Parameter(description = "The UUID of the policy to add a project to", schema = @Schema(type = "string", format = "uuid"), required = true)
@PathParam("policyUuid") @ValidUuid String policyUuid,
Expand Down Expand Up @@ -363,7 +367,10 @@ public Response addTagToPolicy(
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Removes a tag from a policy",
description = "<p>Requires permission <strong>POLICY_MANAGEMENT</strong> or <strong>POLICY_MANAGEMENT_DELETE</strong></p>"
description = """
<p><strong>Deprecated</strong>. Use <code>DELETE /api/v1/tag/{name}/policy</code> instead.</p>
<p>Requires permission <strong>POLICY_MANAGEMENT</strong></p>
"""
)
@ApiResponses(value = {
@ApiResponse(
Expand All @@ -376,6 +383,7 @@ public Response addTagToPolicy(
@ApiResponse(responseCode = "404", description = "The policy or tag could not be found")
})
@PermissionRequired({Permissions.Constants.POLICY_MANAGEMENT, Permissions.Constants.POLICY_MANAGEMENT_DELETE})
@Deprecated(forRemoval = true)
public Response removeTagFromPolicy(
@Parameter(description = "The UUID of the policy to remove the tag from", schema = @Schema(type = "string", format = "uuid"), required = true)
@PathParam("policyUuid") @ValidUuid String policyUuid,
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/org/dependencytrack/resources/v1/TagResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,98 @@ public Response getTaggedPolicies(
return Response.ok(tags).header(TOTAL_COUNT_HEADER, totalCount).build();
}

@POST
@Path("/{name}/policy")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Tags one or more policies.",
description = "<p>Requires permission <strong>POLICY_MANAGEMENT</strong></p>"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "Policies tagged successfully."
),
@ApiResponse(
responseCode = "404",
description = "A tag with the provided name does not exist.",
content = @Content(schema = @Schema(implementation = ProblemDetails.class), mediaType = ProblemDetails.MEDIA_TYPE_JSON)
)
})
@PermissionRequired(Permissions.Constants.POLICY_MANAGEMENT)
public Response tagPolicies(
@Parameter(description = "Name of the tag to assign", required = true)
@PathParam("name") final String tagName,
@Parameter(
description = "UUIDs of policies to tag",
required = true,
array = @ArraySchema(schema = @Schema(type = "string", format = "uuid"))
)
@Size(min = 1, max = 100) final Set<@ValidUuid String> policyUuids
) {
try (final var qm = new QueryManager(getAlpineRequest())) {
qm.tagPolicies(tagName, policyUuids);
} catch (NoSuchElementException nseException) {
// TODO: Move this to an ExceptionMapper once https://github.com/stevespringett/Alpine/pull/588 is available.
return Response
.status(404)
.header("Content-Type", ProblemDetails.MEDIA_TYPE_JSON)
.entity(new ProblemDetails(404, "Resource does not exist", nseException.getMessage()))
.build();
} catch (RuntimeException e) {
throw e;
}

return Response.noContent().build();
}

@DELETE
@Path("/{name}/policy")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Untags one or more policies.",
description = "<p>Requires permission <strong>POLICY_MANAGEMENT</strong></p>"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "Policies untagged successfully."
),
@ApiResponse(
responseCode = "404",
description = "A tag with the provided name does not exist.",
content = @Content(schema = @Schema(implementation = ProblemDetails.class), mediaType = ProblemDetails.MEDIA_TYPE_JSON)
)
})
@PermissionRequired(Permissions.Constants.POLICY_MANAGEMENT)
public Response untagPolicies(
@Parameter(description = "Name of the tag", required = true)
@PathParam("name") final String tagName,
@Parameter(
description = "UUIDs of policies to untag",
required = true,
array = @ArraySchema(schema = @Schema(type = "string", format = "uuid"))
)
@Size(min = 1, max = 100) final Set<@ValidUuid String> policyUuids
) {
try (final var qm = new QueryManager(getAlpineRequest())) {
qm.untagPolicies(tagName, policyUuids);
} catch (NoSuchElementException nseException) {
// TODO: Move this to an ExceptionMapper once https://github.com/stevespringett/Alpine/pull/588 is available.
return Response
.status(404)
.header("Content-Type", ProblemDetails.MEDIA_TYPE_JSON)
.entity(new ProblemDetails(404, "Resource does not exist", nseException.getMessage()))
.build();
} catch (RuntimeException e) {
throw e;
}

return Response.noContent().build();
}

@GET
@Path("/policy/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
Expand Down
Loading

0 comments on commit f98b6b2

Please sign in to comment.