Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -8,6 +8,7 @@
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
Expand Down Expand Up @@ -36,6 +37,12 @@
@Path("api/collections")
public class DarCollectionResource extends Resource {

private static final String CANCEL_ROLE_ERROR =
"Only chairpersons and researchers can cancel a collection";
Comment thread
kevinmarete marked this conversation as resolved.
Outdated
private static final String CHAIR_ROLE_REQUIRED_ERROR =
"roleName=" + CHAIRPERSON + " is required to cancel the elections for a collection";
private static final String CREATE_ELECTION_ROLE_ERROR = "Only chairpersons can create elections";

private final DarCollectionService darCollectionService;

@Inject
Expand Down Expand Up @@ -193,23 +200,33 @@ public Response getCollectionByReferenceId(
@PUT
@Path("{id}/cancel")
@Produces("application/json")
@RolesAllowed({ADMIN, CHAIRPERSON, RESEARCHER})
@RolesAllowed({CHAIRPERSON, RESEARCHER})
public Response cancelDarCollectionByCollectionId(
@Auth DuosUser duosUser,
@Context Request request,
@PathParam("id") Integer collectionId,
@QueryParam("roleName") String roleName) {
try {
User user = duosUser.getUser();
DarCollection collection = darCollectionService.getByCollectionId(user, collectionId);
isCollectionPresent(collection);

// Default to the least impactful role if none provided.
UserRoles actingRole = UserRoles.RESEARCHER;
if (roleName != null) {
UserRoles actingRole;
if (roleName == null) {
// Only the least impactful role is implied.
if (user.hasUserRole(UserRoles.RESEARCHER)) {
actingRole = UserRoles.RESEARCHER;
} else if (user.hasUserRole(UserRoles.CHAIRPERSON)) {
throw new BadRequestException(CHAIR_ROLE_REQUIRED_ERROR);
} else {
throw new ForbiddenException(CANCEL_ROLE_ERROR);
}
} else {
actingRole = validateUserHasRoleName(user, roleName);
if (actingRole != UserRoles.CHAIRPERSON && actingRole != UserRoles.RESEARCHER) {
throw new ForbiddenException(CANCEL_ROLE_ERROR);
}
}

DarCollection collection = darCollectionService.getByCollectionId(user, collectionId);
isCollectionPresent(collection);
DarCollection cancelledCollection =
darCollectionService.cancelDarCollectionByRole(user, collection, actingRole);
ComplianceLogger.logDARCancellation(
Expand Down Expand Up @@ -253,6 +270,9 @@ public Response createElectionsForCollection(
@Context Request request) {
try {
User user = duosUser.getUser();
if (!user.hasUserRole(UserRoles.CHAIRPERSON)) {
throw new ForbiddenException(CREATE_ELECTION_ROLE_ERROR);
}
DarCollection sourceCollection = darCollectionService.getByCollectionId(user, collectionId);
isCollectionPresent(sourceCollection);
DarCollection updatedCollection =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@

public class DarCollectionService implements ConsentLogger {

private static final String CANCEL_ROLE_ERROR =
"Only chairpersons and researchers can cancel a collection";
private static final String CREATE_ELECTION_ROLE_ERROR = "Only chairpersons can create elections";
private static final String CREATE_ELECTION_DAC_ERROR =
"User is not a chairperson for any dataset in this collection";

private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
private final DacDAO dacDAO;
private final DaaDAO daaDAO;
Expand Down Expand Up @@ -139,30 +145,18 @@
}

private void processDarCollectionSummariesForAdmin(List<DarCollectionSummary> summaries) {
// if at least one election is open, show cancel
// if at least one non-open/absent election, show open
summaries.forEach(
s -> {
// Admins are read-only.
s.getActions().clear();
Map<String, Integer> statusCount = new HashMap<>();
Map<Integer, Election> elections = s.getElections();
if (elections.isEmpty()) {
s.setStatus(DarCollectionStatus.SUBMITTED.getValue());
} else {
elections
.values()
.forEach(
e -> {
String status = e.getStatus();
updateStatusCount(statusCount, status);
if (status.equals(ElectionStatus.OPEN.getValue())) {
s.addAction(DarCollectionActions.CANCEL);
}
});
elections.values().forEach(e -> updateStatusCount(statusCount, e.getStatus()));
determineCollectionStatus(s, statusCount);
}
if (s.getCloseoutSupplement() != null) {
s.getActions().clear();
}
});
}

Expand Down Expand Up @@ -654,15 +648,18 @@

/**
* Cancel Elections or a dar for a DarCollection, given a user and a role. If the user is a chair,
* or admin, cancel elections. If the user is a researcher, cancel the dar.
* cancel elections. If the user is a researcher, cancel the dar.
*
* @param user The User initiating the cancel
* @param collection The DarCollection
* @param role The role of the user, must be one of ADMIN, CHAIRPERSON, or RESEARCHER
* @param role The role of the user, must be CHAIRPERSON or RESEARCHER
* @return The DarCollection that has been canceled
*/
public DarCollection cancelDarCollectionByRole(
User user, DarCollection collection, UserRoles role) {
if (role != UserRoles.CHAIRPERSON && role != UserRoles.RESEARCHER) {
throw new ForbiddenException(CANCEL_ROLE_ERROR);
}
Collection<DataAccessRequest> dars = collection.getDars().values();
if (dars.isEmpty()) {
logWarn(
Expand All @@ -672,20 +669,18 @@
}

DarCollection cancelledCollection =
switch (role) {
case ADMIN -> cancelDarCollectionElectionsAsAdmin(collection, user);
case CHAIRPERSON -> cancelDarCollectionElectionsAsChair(collection, user);
default -> cancelDarCollectionAsResearcher(collection, user);
};
role == UserRoles.CHAIRPERSON
? cancelDarCollectionElectionsAsChair(collection, user)
: cancelDarCollectionAsResearcher(collection, user);
return getByCollectionId(user, cancelledCollection.getDarCollectionId());
}

/**
* Cancel a DarCollection as a researcher.
*
* <p>If an election exists for a DAR within the collection, that DAR cannot be cancelled by the
* researcher. Since it's now under DAC review, it's up to the DAC Chair (or admin) to ultimately
* decline or cancel the elections for the collection.
* researcher. Since it's now under DAC review, it's up to the DAC Chair to ultimately decline or
* cancel the elections for the collection.
*
* @param collection The DarCollection
* @param user the researcher requesting the cancel
Expand Down Expand Up @@ -723,24 +718,6 @@
return getByCollectionId(user, collection.getDarCollectionId());
}

/**
* Cancel Elections for a DarCollection as an admin.
*
* <p>Admins can cancel all elections in a DarCollection
*
* @param collection The DarCollection
* @return The DarCollection whose elections have been canceled
*/
private DarCollection cancelDarCollectionElectionsAsAdmin(DarCollection collection, User user) {
Collection<DataAccessRequest> dars = collection.getDars().values();
List<String> referenceIds = dars.stream().map(DataAccessRequest::getReferenceId).toList();

// Cancel all DAR elections
cancelElectionsForReferenceIds(referenceIds);

return getByCollectionId(user, collection.getDarCollectionId());
}

/**
* Cancel Elections for a DarCollection as a chairperson.
*
Expand Down Expand Up @@ -805,9 +782,20 @@
* @param collection The DarCollection
* @return The updated DarCollection
*/
public DarCollection createElectionsForDarCollection(User user, DarCollection collection)

Check failure on line 785 in src/main/java/org/broadinstitute/consent/http/service/DarCollectionService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=DataBiosphere_consent&issues=AZ_9OJ3CyvrdxWitP2sC&open=AZ_9OJ3CyvrdxWitP2sC&pullRequest=3019
throws BadRequestException, ForbiddenException, ConsentConflictException, SQLException {
if (!user.hasUserRole(UserRoles.CHAIRPERSON)) {
throw new ForbiddenException(CREATE_ELECTION_ROLE_ERROR);
}
DataAccessRequest dar = collection.getMostRecentDar();
List<Integer> darDatasetIds = dar.getDatasetIds();

Check failure on line 791 in src/main/java/org/broadinstitute/consent/http/service/DarCollectionService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Fix this access that will throw a NullPointerException when executed.

See more on https://sonarcloud.io/project/issues?id=DataBiosphere_consent&issues=AZ_9OJ3CyvrdxWitP2sD&open=AZ_9OJ3CyvrdxWitP2sD&pullRequest=3019
if (!darDatasetIds.isEmpty()) {
Set<Integer> governedDatasetIds =
Set.copyOf(datasetDAO.findDatasetIdsByDACUserId(user.getUserId()));
if (darDatasetIds.stream().noneMatch(governedDatasetIds::contains)) {
throw new ForbiddenException(CREATE_ELECTION_DAC_ERROR);
}
}
if ((!dar.getRequiresSOApproval() || dar.getApprovingSigningOfficialUserId() != null)) {
try {
List<String> createdElectionReferenceIds =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
put:
summary: Cancel DAR Collection By ID
operationId: apiCollectionsCollectionIdCancelPut
description: Returns DAR Collection after canceling associated dars
description: Returns DAR Collection after a Researcher cancels their dars or a Chairperson cancels the elections they govern. Admins have no access and escalate to the governing DAC Chairperson.
tags:
- DAR Collection
parameters:
Expand All @@ -13,14 +13,11 @@ put:
type: integer
- name: roleName
in: query
description: |
String value that represents the role name to act as. If not provided,
the system will cancel the collection and associated elections as the
least permissioned role the user has (researcher) for safety.
description: Role name to act as. Defaults to Researcher; Chairperson must be requested explicitly.
required: false
schema:
type: string
enum: [Admin, Chairperson, Researcher]
enum: [Chairperson, Researcher]
responses:
200:
description: Returns target dar collection with DARs canceled
Expand All @@ -29,7 +26,9 @@ put:
schema:
$ref: '../schemas/DarCollection.yaml'
400:
description: Bad Request (result of elections present on DARs or the user not having the provided role name)
description: Bad Request (result of elections present on DARs, or a role name that is missing or invalid for this user)
403:
description: Forbidden. Not acting as a Chairperson or Researcher.
404:
description: Not Found
429:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
post:
summary: Create Elections for Collection By ID
operationId: apiCollectionsCollectionIdElectionPost
description: Returns created Elections for Dars in Collection
description: Returns created Elections for Dars in Collection, for the datasets the Chairperson governs. Limited to Chairpersons; Admins have no access and escalate to the governing DAC Chairperson.
tags:
- DAR Collection
- Election
Expand All @@ -22,7 +22,9 @@ post:
400:
description: Bad Request. Check error details
401:
description: Not authorized to perform this action.
description: Authentication required.
403:
description: Forbidden. Not a Chairperson for any dataset in the collection.
404:
description: Not Found
409:
Expand Down
Loading
Loading