Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port : Add REST endpoints to tag and untag policies in bulk #830

Merged
merged 1 commit into from
Aug 7, 2024
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
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