• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DataBiosphere / consent / #5697

18 Apr 2025 04:23PM UTC coverage: 78.956% (-0.02%) from 78.975%
#5697

push

web-flow
[DT-1487]Req. 10 - Enable Int. Collab. Approvals via DAR when LCA = Yes (#2484)

Co-authored-by: rjohanek <rjohanek@broadinstitute.org>

49 of 50 new or added lines in 5 files covered. (98.0%)

14 existing lines in 4 files now uncovered.

10209 of 12930 relevant lines covered (78.96%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.0
/src/main/java/org/broadinstitute/consent/http/resources/DataAccessRequestResource.java
1
package org.broadinstitute.consent.http.resources;
2

3
import com.google.cloud.storage.BlobId;
4
import com.google.common.base.Strings;
5
import com.google.inject.Inject;
6
import io.dropwizard.auth.Auth;
7
import jakarta.annotation.security.PermitAll;
8
import jakarta.annotation.security.RolesAllowed;
9
import jakarta.ws.rs.BadRequestException;
10
import jakarta.ws.rs.Consumes;
11
import jakarta.ws.rs.DELETE;
12
import jakarta.ws.rs.ForbiddenException;
13
import jakarta.ws.rs.GET;
14
import jakarta.ws.rs.NotFoundException;
15
import jakarta.ws.rs.POST;
16
import jakarta.ws.rs.PUT;
17
import jakarta.ws.rs.Path;
18
import jakarta.ws.rs.PathParam;
19
import jakarta.ws.rs.Produces;
20
import jakarta.ws.rs.core.Context;
21
import jakarta.ws.rs.core.MediaType;
22
import jakarta.ws.rs.core.Response;
23
import jakarta.ws.rs.core.StreamingOutput;
24
import jakarta.ws.rs.core.UriInfo;
25
import java.io.IOException;
26
import java.io.InputStream;
27
import java.net.URI;
28
import java.util.Collections;
29
import java.util.List;
30
import java.util.Objects;
31
import java.util.UUID;
32
import java.util.stream.Collectors;
33
import java.util.stream.Stream;
34
import org.apache.commons.lang3.StringUtils;
35
import org.broadinstitute.consent.http.cloudstore.GCSService;
36
import org.broadinstitute.consent.http.enumeration.DarDocumentType;
37
import org.broadinstitute.consent.http.enumeration.UserRoles;
38
import org.broadinstitute.consent.http.exceptions.LibraryCardRequiredException;
39
import org.broadinstitute.consent.http.models.AuthUser;
40
import org.broadinstitute.consent.http.models.DataAccessAgreement;
41
import org.broadinstitute.consent.http.models.DataAccessRequest;
42
import org.broadinstitute.consent.http.models.DataAccessRequestData;
43
import org.broadinstitute.consent.http.models.DataUse;
44
import org.broadinstitute.consent.http.models.Dataset;
45
import org.broadinstitute.consent.http.models.Error;
46
import org.broadinstitute.consent.http.models.User;
47
import org.broadinstitute.consent.http.service.DaaService;
48
import org.broadinstitute.consent.http.service.DataAccessRequestService;
49
import org.broadinstitute.consent.http.service.DatasetService;
50
import org.broadinstitute.consent.http.service.EmailService;
51
import org.broadinstitute.consent.http.service.MatchService;
52
import org.broadinstitute.consent.http.service.UserService;
53
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
54
import org.glassfish.jersey.media.multipart.FormDataParam;
55

56
@Path("api/dar")
57
public class DataAccessRequestResource extends Resource {
58

59
  private final DaaService daaService;
60
  private final DataAccessRequestService dataAccessRequestService;
61
  private final EmailService emailService;
62
  private final GCSService gcsService;
63
  private final MatchService matchService;
64
  private final UserService userService;
65
  private final DatasetService datasetService;
66

67
  @Inject
68
  public DataAccessRequestResource(
69
      DaaService daaService,
70
      DataAccessRequestService dataAccessRequestService,
71
      EmailService emailService,
72
      GCSService gcsService,
73
      UserService userService,
74
      DatasetService datasetService,
75
      MatchService matchService
76
  ) {
1✔
77
    this.daaService = daaService;
1✔
78
    this.dataAccessRequestService = dataAccessRequestService;
1✔
79
    this.emailService = emailService;
1✔
80
    this.gcsService = gcsService;
1✔
81
    this.userService = userService;
1✔
82
    this.datasetService = datasetService;
1✔
83
    this.matchService = matchService;
1✔
84
  }
1✔
85

86
  @GET
87
  @Produces("application/json")
88
  @PermitAll
89
  @Path("/v2")
90
  public Response getDataAccessRequests(@Auth AuthUser authUser) {
91
    try {
92
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
93
      List<DataAccessRequest> dars = dataAccessRequestService.getDataAccessRequestsByUserRole(user);
1✔
94
      return Response.ok().entity(dars).build();
1✔
95
    } catch (Exception e) {
×
96
      return createExceptionResponse(e);
×
97
    }
98
  }
99

100
  @POST
101
  @Consumes("application/json")
102
  @Produces("application/json")
103
  @RolesAllowed(RESEARCHER)
104
  @Path("/v2")
105
  public Response createDataAccessRequest(
106
      @Auth AuthUser authUser, @Context UriInfo info, String dar) {
107
    try {
108
      User user = findUserByEmail(authUser.getEmail());
1✔
109

110
      DataAccessRequest payload = populateDarFromJsonString(user, dar);
1✔
111
      DataAccessRequest newDar = dataAccessRequestService.createDataAccessRequest(user, payload);
1✔
112
      Integer collectionId = newDar.getCollectionId();
1✔
113
      try {
114
        emailService.sendNewDARCollectionMessage(collectionId);
1✔
115
      } catch (Exception e) {
×
116
        // non-fatal exception
117
        logException("Exception sending email for collection id: " + collectionId, e);
×
118
      }
1✔
119
      URI uri = info.getRequestUriBuilder().build();
1✔
120
      matchService.reprocessMatchesForPurpose(newDar.getReferenceId());
1✔
121
      return Response.created(uri).entity(newDar.convertToSimplifiedDar()).build();
1✔
UNCOV
122
    } catch (Exception e) {
×
UNCOV
123
      return createExceptionResponse(e);
×
124
    }
125
  }
126

127
  @POST
128
  @Consumes("application/json")
129
  @Produces("application/json")
130
  @RolesAllowed(RESEARCHER)
131
  @Path("/v3")
132
  public Response createDataAccessRequestWithDAARestrictions(
133
      @Auth AuthUser authUser, @Context UriInfo info, String dar) {
134
    try {
135
      User user = findUserByEmail(authUser.getEmail());
1✔
136
      DataAccessRequest payload = populateDarFromJsonString(user, dar);
1✔
137
      // DAA Enforcement
138
      datasetService.enforceDAARestrictions(user, payload.getDatasetIds());
1✔
139
      DataAccessRequest newDar = dataAccessRequestService.createDataAccessRequest(user, payload);
1✔
140
      Integer collectionId = newDar.getCollectionId();
1✔
141
      try {
142
        emailService.sendNewDARCollectionMessage(collectionId);
1✔
143
      } catch (Exception e) {
×
144
        // non-fatal exception
145
        logException("Exception sending email for collection id: " + collectionId, e);
×
146
      }
1✔
147
      URI uri = info.getRequestUriBuilder().build();
1✔
148
      matchService.reprocessMatchesForPurpose(newDar.getReferenceId());
1✔
149
      return Response.created(uri).entity(newDar.convertToSimplifiedDar()).build();
1✔
150
    } catch (Exception e) {
1✔
151
      return createExceptionResponse(e);
1✔
152
    }
153
  }
154

155
  @GET
156
  @Path("/v2/{referenceId}")
157
  @Produces("application/json")
158
  @PermitAll
159
  public Response getByReferenceId(
160
      @Auth AuthUser authUser, @PathParam("referenceId") String referenceId) {
161
    validateAuthedRoleUser(
1✔
162
        Stream.of(UserRoles.ADMIN, UserRoles.CHAIRPERSON, UserRoles.MEMBER)
1✔
163
            .collect(Collectors.toList()),
1✔
164
        authUser, referenceId);
165
    try {
166
      DataAccessRequest dar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
167
      if (Objects.nonNull(dar)) {
1✔
168
        return Response.status(Response.Status.OK).entity(dar.convertToSimplifiedDar()).build();
1✔
169
      }
170
      return Response.status(Response.Status.NOT_FOUND)
×
171
          .entity(
×
172
              new Error(
173
                  "Unable to find Data Access Request with reference id: " + referenceId,
174
                  Response.Status.NOT_FOUND.getStatusCode()))
×
175
          .build();
×
176
    } catch (Exception e) {
×
177
      return createExceptionResponse(e);
×
178
    }
179
  }
180

181
  @GET
182
  @Path("/v2/{referenceId}/daas")
183
  @Produces("application/json")
184
  @PermitAll
185
  public Response getDAAsByReferenceId(
186
      @Auth AuthUser authUser, @PathParam("referenceId") String referenceId) {
187
    try {
188
      validateAuthedRoleUser(
1✔
189
          Stream.of(UserRoles.ADMIN, UserRoles.CHAIRPERSON, UserRoles.MEMBER)
1✔
190
              .collect(Collectors.toList()),
1✔
191
          authUser, referenceId);
192
      List<DataAccessAgreement> dataAccessAgreements = daaService.findByDarReferenceId(referenceId);
1✔
193
      return Response.status(Response.Status.OK).entity(dataAccessAgreements).build();
1✔
194
    } catch (Exception e) {
1✔
195
      return createExceptionResponse(e);
1✔
196
    }
197
  }
198

199
  @PUT
200
  @Path("/v2/{referenceId}")
201
  @Produces("application/json")
202
  @RolesAllowed(RESEARCHER)
203
  public Response updateByReferenceId(
204
      @Auth AuthUser authUser, @PathParam("referenceId") String referenceId, String dar) {
205
    try {
206
      User user = findUserByEmail(authUser.getEmail());
1✔
207
      DataAccessRequest originalDar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
208
      checkAuthorizedUpdateUser(user, originalDar);
1✔
209
      DataAccessRequestData data = DataAccessRequestData.fromString(dar);
1✔
210
      // Keep dar data reference id in sync with the dar until we fully deprecate
211
      // it in dar data.
212
      data.setReferenceId(originalDar.getReferenceId());
1✔
213
      originalDar.setData(data);
1✔
214
      DataAccessRequest updatedDar =
1✔
215
          dataAccessRequestService.updateByReferenceId(user, originalDar);
1✔
216
      matchService.reprocessMatchesForPurpose(referenceId);
1✔
217
      return Response.ok().entity(updatedDar.convertToSimplifiedDar()).build();
1✔
218
    } catch (Exception e) {
1✔
219
      return createExceptionResponse(e);
1✔
220
    }
221
  }
222

223
  @GET
224
  @Produces("application/json")
225
  @Path("/v2/draft")
226
  @RolesAllowed(RESEARCHER)
227
  public Response getDraftDataAccessRequests(@Auth AuthUser authUser) {
228
    try {
229
      User user = findUserByEmail(authUser.getEmail());
1✔
230
      List<DataAccessRequest> draftDars = dataAccessRequestService.findAllDraftDataAccessRequestsByUser(
1✔
231
          user.getUserId());
1✔
232
      return Response.ok().entity(draftDars).build();
1✔
233
    } catch (Exception e) {
1✔
234
      return createExceptionResponse(e);
1✔
235
    }
236
  }
237

238
  @GET
239
  @Produces("application/json")
240
  @Path("/v2/draft/{referenceId}")
241
  @RolesAllowed(RESEARCHER)
242
  public Response getDraftDar(@Auth AuthUser authUser, @PathParam("referenceId") String id) {
243
    try {
244
      User user = findUserByEmail(authUser.getEmail());
1✔
245
      DataAccessRequest dar = dataAccessRequestService.findByReferenceId(id);
1✔
246
      if (dar.getUserId().equals(user.getUserId())) {
1✔
247
        return Response.ok().entity(dar).build();
1✔
248
      }
249
      throw new ForbiddenException("User does not have permission");
1✔
250
    } catch (Exception e) {
1✔
251
      return createExceptionResponse(e);
1✔
252
    }
253
  }
254

255
  @POST
256
  @Consumes("application/json")
257
  @Produces("application/json")
258
  @Path("/v2/draft")
259
  @RolesAllowed(RESEARCHER)
260
  public Response createDraftDataAccessRequest(
261
      @Auth AuthUser authUser, @Context UriInfo info, String dar) {
262
    try {
263
      User user = findUserByEmail(authUser.getEmail());
1✔
264
      DataAccessRequest newDar = populateDarFromJsonString(user, dar);
1✔
265
      DataAccessRequest result =
1✔
266
          dataAccessRequestService.insertDraftDataAccessRequest(user, newDar);
1✔
267
      URI uri = info.getRequestUriBuilder().path("/" + result.getReferenceId()).build();
1✔
268
      return Response.created(uri).entity(result.convertToSimplifiedDar()).build();
1✔
269
    } catch (Exception e) {
×
270
      return createExceptionResponse(e);
×
271
    }
272
  }
273

274
  @POST
275
  @Consumes("application/json")
276
  @Produces("application/json")
277
  @Path("/v3/draft")
278
  @RolesAllowed(RESEARCHER)
279
  public Response createDraftDataAccessRequestWithDAARestrictions(
280
      @Auth AuthUser authUser, @Context UriInfo info, String dar) {
281
    try {
282
      User user = findUserByEmail(authUser.getEmail());
1✔
283
      DataAccessRequest newDar = populateDarFromJsonString(user, dar);
1✔
284
      // DAA Enforcement
285
      datasetService.enforceDAARestrictions(user, newDar.getDatasetIds());
1✔
286
      DataAccessRequest result =
1✔
287
          dataAccessRequestService.insertDraftDataAccessRequest(user, newDar);
1✔
288
      URI uri = info.getRequestUriBuilder().path("/" + result.getReferenceId()).build();
1✔
289
      return Response.created(uri).entity(result.convertToSimplifiedDar()).build();
1✔
290
    } catch (Exception e) {
1✔
291
      return createExceptionResponse(e);
1✔
292
    }
293
  }
294

295
  @PUT
296
  @Consumes("application/json")
297
  @Produces("application/json")
298
  @Path("/v2/draft/{referenceId}")
299
  @RolesAllowed(RESEARCHER)
300
  public Response updatePartialDataAccessRequest(
301
      @Auth AuthUser authUser, @PathParam("referenceId") String referenceId, String dar) {
302
    try {
303
      User user = findUserByEmail(authUser.getEmail());
1✔
304
      DataAccessRequest originalDar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
305
      checkAuthorizedUpdateUser(user, originalDar);
1✔
306
      DataAccessRequestData data = DataAccessRequestData.fromString(dar);
1✔
307
      // Keep dar data reference id in sync with the dar until we fully deprecate
308
      // it in dar data.
309
      data.setReferenceId(originalDar.getReferenceId());
1✔
310
      originalDar.setData(data);
1✔
311
      originalDar.setDatasetIds(data.getDatasetIds());
1✔
312
      DataAccessRequest updatedDar =
1✔
313
          dataAccessRequestService.updateByReferenceId(user, originalDar);
1✔
314
      return Response.ok().entity(updatedDar.convertToSimplifiedDar()).build();
1✔
315
    } catch (Exception e) {
1✔
316
      return createExceptionResponse(e);
1✔
317
    }
318
  }
319

320
  @PUT
321
  @Consumes("application/json")
322
  @Produces("application/json")
323
  @Path("/v3/draft/{referenceId}")
324
  @RolesAllowed(RESEARCHER)
325
  public Response updatePartialDataAccessRequestWithDAARestrictions(
326
      @Auth AuthUser authUser, @PathParam("referenceId") String referenceId, String dar) {
327
    try {
328
      User user = findUserByEmail(authUser.getEmail());
1✔
329
      DataAccessRequest originalDar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
330
      checkAuthorizedUpdateUser(user, originalDar);
1✔
331
      DataAccessRequestData data = DataAccessRequestData.fromString(dar);
1✔
332
      // Keep dar data reference id in sync with the dar until we fully deprecate
333
      // it in dar data.
334
      data.setReferenceId(originalDar.getReferenceId());
1✔
335
      originalDar.setData(data);
1✔
336
      originalDar.setDatasetIds(data.getDatasetIds());
1✔
337
      // DAA Enforcement
338
      datasetService.enforceDAARestrictions(user, originalDar.getDatasetIds());
1✔
339
      DataAccessRequest updatedDar =
1✔
340
          dataAccessRequestService.updateByReferenceId(user, originalDar);
1✔
341
      return Response.ok().entity(updatedDar.convertToSimplifiedDar()).build();
1✔
342
    } catch (Exception e) {
1✔
343
      return createExceptionResponse(e);
1✔
344
    }
345
  }
346

347
  @GET
348
  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
349
  @Path("/v2/{referenceId}/irbDocument")
350
  @RolesAllowed({ADMIN, CHAIRPERSON, MEMBER, RESEARCHER})
351
  public Response getIrbDocument(
352
      @Auth AuthUser authUser,
353
      @PathParam("referenceId") String referenceId) {
354
    try {
355
      DataAccessRequest dar = getDarById(referenceId);
1✔
356
      validateAuthedRoleUser(
1✔
357
          Stream.of(UserRoles.ADMIN, UserRoles.CHAIRPERSON, UserRoles.MEMBER)
1✔
358
              .collect(Collectors.toList()),
1✔
359
          authUser, referenceId);
360
      if (dar.getData() != null &&
1✔
361
          StringUtils.isNotEmpty(dar.getData().getIrbDocumentLocation()) &&
1✔
362
          StringUtils.isNotEmpty(dar.getData().getIrbDocumentName())
1✔
363
      ) {
364
        String blobIdName = dar.getData().getIrbDocumentLocation();
1✔
365
        String fileName = dar.getData().getIrbDocumentName();
1✔
366
        InputStream is = gcsService.getDocument(blobIdName);
1✔
367
        StreamingOutput stream = createStreamingOutput(is);
1✔
368
        return Response.ok(stream)
1✔
369
            .header("Content-Disposition", "attachment; filename=" + fileName)
1✔
370
            .build();
1✔
371
      }
372
      throw new NotFoundException();
1✔
373
    } catch (Exception e) {
1✔
374
      return createExceptionResponse(e);
1✔
375
    }
376
  }
377

378
  @POST
379
  @Consumes(MediaType.MULTIPART_FORM_DATA)
380
  @Produces(MediaType.APPLICATION_JSON)
381
  @Path("/v2/{referenceId}/irbDocument")
382
  @RolesAllowed({RESEARCHER})
383
  public Response uploadIrbDocument(
384
      @Auth AuthUser authUser,
385
      @PathParam("referenceId") String referenceId,
386
      @FormDataParam("file") InputStream uploadInputStream,
387
      @FormDataParam("file") FormDataContentDisposition fileDetail) {
388
    try {
389
      User user = findUserByEmail(authUser.getEmail());
1✔
390
      DataAccessRequest dar = getDarById(referenceId);
1✔
391
      checkAuthorizedUpdateUser(user, dar);
1✔
392
      DataAccessRequest updatedDar = updateDarWithDocumentContents(DarDocumentType.IRB, user, dar,
1✔
393
          uploadInputStream, fileDetail);
394
      return Response.ok(updatedDar.convertToSimplifiedDar()).build();
1✔
395
    } catch (Exception e) {
1✔
396
      return createExceptionResponse(e);
1✔
397
    }
398
  }
399

400
  @POST
401
  @Consumes(MediaType.MULTIPART_FORM_DATA)
402
  @Produces(MediaType.APPLICATION_JSON)
403
  @Path("/v2/progress_report/{parentReferenceId}")
404
  @RolesAllowed({RESEARCHER})
405
  public Response postProgressReport(
406
      @Auth AuthUser authUser,
407
      @PathParam("parentReferenceId") String parentReferenceId,
408
      @FormDataParam("dar") String dar,
409
      @FormDataParam("collaboratorRequiredFile") InputStream collabInputStream,
410
      @FormDataParam("collaboratorRequiredFile") FormDataContentDisposition collabFileDetails,
411
      @FormDataParam("ethicsApprovalRequiredFile") InputStream ethicsInputStream,
412
      @FormDataParam("ethicsApprovalRequiredFile") FormDataContentDisposition ethicsFileDetails) {
413
    try {
414
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
415
      DataAccessRequest parentDar = dataAccessRequestService.findByReferenceId(parentReferenceId);
1✔
416
      if (!user.getUserId().equals(parentDar.getUserId())) {
1✔
417
        throw new ForbiddenException("User not authorized to update this Data Access Request");
1✔
418
      }
419

420
      DataAccessRequest payload = populateDarFromJsonString(user, dar);
1✔
421
      DataAccessRequest childDar = dataAccessRequestService.createDataAccessRequest(user, payload);
1✔
422

423
      for (Integer datasetId : childDar.getDatasetIds()) {
1✔
424
        Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
425
        if (dataset == null) {
1✔
426
          throw new NotFoundException("Dataset " + datasetId + " not found");
1✔
427
        }
428
        DataUse dataUse = dataset.getDataUse();
1✔
429
        if (dataUse == null || dataUse.getCollaboratorRequired() == null
1✔
430
            || dataUse.getEthicsApprovalRequired() == null) {
1✔
431
          throw new BadRequestException("Dataset " + datasetId + " is missing data use(s)");
1✔
432
        }
433
        if (dataUse.getCollaboratorRequired()) {
1✔
434
          String parentCollabLocation = parentDar.getData().getCollaborationLetterLocation();
1✔
435
          if ((collabFileDetails == null || collabFileDetails.getSize() <= 0)
1✔
436
              && Strings.isNullOrEmpty(parentCollabLocation)) {
1✔
437
            throw new BadRequestException("Collaboration document is required");
1✔
438
          }
439
          childDar = updateDarWithDocumentContents(DarDocumentType.COLLABORATION, user, childDar,
1✔
440
              collabInputStream, collabFileDetails);
441
        }
442
        if (dataUse.getEthicsApprovalRequired()) {
1✔
443
          String parentEthicsLocation = parentDar.getData().getIrbDocumentLocation();
1✔
444
          if ((ethicsFileDetails == null || ethicsFileDetails.getSize() <= 0)
1✔
445
              && Strings.isNullOrEmpty(parentEthicsLocation)) {
1✔
446
            throw new BadRequestException("Ethics approval document is required");
1✔
447
          }
448
          childDar = updateDarWithDocumentContents(DarDocumentType.IRB, user, childDar,
1✔
449
              ethicsInputStream, ethicsFileDetails);
450
        }
451
      }
1✔
452
      return Response.ok(childDar.convertToSimplifiedDar()).build();
1✔
453
    } catch (Exception e) {
1✔
454
      return createExceptionResponse(e);
1✔
455
    }
456
  }
457

458
  @GET
459
  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
460
  @Path("/v2/{referenceId}/collaborationDocument")
461
  @RolesAllowed({ADMIN, CHAIRPERSON, MEMBER, RESEARCHER})
462
  public Response getCollaborationDocument(
463
      @Auth AuthUser authUser,
464
      @PathParam("referenceId") String referenceId) {
465
    try {
466
      DataAccessRequest dar = getDarById(referenceId);
1✔
467
      validateAuthedRoleUser(
1✔
468
          Stream.of(UserRoles.ADMIN, UserRoles.CHAIRPERSON, UserRoles.MEMBER)
1✔
469
              .collect(Collectors.toList()),
1✔
470
          authUser, referenceId);
471
      if (dar.getData() != null &&
1✔
472
          StringUtils.isNotEmpty(dar.getData().getCollaborationLetterLocation()) &&
1✔
473
          StringUtils.isNotEmpty(dar.getData().getCollaborationLetterName())
1✔
474
      ) {
475
        String blobIdName = dar.getData().getCollaborationLetterLocation();
1✔
476
        String fileName = dar.getData().getCollaborationLetterName();
1✔
477
        InputStream is = gcsService.getDocument(blobIdName);
1✔
478
        StreamingOutput stream = createStreamingOutput(is);
1✔
479
        return Response.ok(stream)
1✔
480
            .header("Content-Disposition", "attachment; filename=" + fileName)
1✔
481
            .build();
1✔
482
      }
483
      throw new NotFoundException();
1✔
484
    } catch (Exception e) {
1✔
485
      return createExceptionResponse(e);
1✔
486
    }
487
  }
488

489
  @POST
490
  @Consumes(MediaType.MULTIPART_FORM_DATA)
491
  @Produces(MediaType.APPLICATION_JSON)
492
  @Path("/v2/{referenceId}/collaborationDocument")
493
  @RolesAllowed({RESEARCHER})
494
  public Response uploadCollaborationDocument(
495
      @Auth AuthUser authUser,
496
      @PathParam("referenceId") String referenceId,
497
      @FormDataParam("file") InputStream uploadInputStream,
498
      @FormDataParam("file") FormDataContentDisposition fileDetail) {
499
    try {
500
      User user = findUserByEmail(authUser.getEmail());
1✔
501
      DataAccessRequest dar = getDarById(referenceId);
1✔
502
      checkAuthorizedUpdateUser(user, dar);
1✔
503
      DataAccessRequest updatedDar = updateDarWithDocumentContents(DarDocumentType.COLLABORATION,
1✔
504
          user, dar, uploadInputStream, fileDetail);
505
      return Response.ok(updatedDar.convertToSimplifiedDar()).build();
1✔
506
    } catch (Exception e) {
1✔
507
      return createExceptionResponse(e);
1✔
508
    }
509
  }
510

511
  @DELETE
512
  @Path("/v2/{referenceId}")
513
  @Produces("application/json")
514
  @RolesAllowed({ADMIN, RESEARCHER})
515
  public Response deleteDar(@Auth AuthUser authUser, @PathParam("referenceId") String referenceId) {
516
    validateAuthedRoleUser(Collections.singletonList(UserRoles.ADMIN), authUser, referenceId);
×
517
    try {
518
      User user = findUserByEmail(authUser.getEmail());
×
519
      dataAccessRequestService.deleteByReferenceId(user, referenceId);
×
520
      return Response.ok().build();
×
521
    } catch (Exception e) {
×
522
      return createExceptionResponse(e);
×
523
    }
524
  }
525

526
  private User findUserByEmail(String email) {
527
    User user = userService.findUserByEmail(email);
1✔
528
    if (user == null) {
1✔
529
      throw new NotFoundException("Unable to find User with the provided email: " + email);
×
530
    }
531
    return user;
1✔
532
  }
533

534
  private DataAccessRequest populateDarFromJsonString(User user, String json) {
535
    DataAccessRequest newDar = new DataAccessRequest();
1✔
536
    DataAccessRequestData data;
537
    try {
538
      data = DataAccessRequestData.fromString(json);
1✔
539
    } catch (Exception e) {
1✔
540
      throw new BadRequestException("Unable to parse DAR from JSON string");
1✔
541
    }
1✔
542
    if (Objects.isNull(data)) {
1✔
543
      data = new DataAccessRequestData();
1✔
544
    }
545
    // When posting a submitted dar, there are two cases:
546
    // 1. those that existed previously as a draft dar
547
    // 2. those that are brand new
548
    // Validate the provided referenceId with the authenticated user and draft status
549
    // Those that do not validate are considered a brand new dar
550
    if (Objects.nonNull(data.getReferenceId())) {
1✔
551
      DataAccessRequest existingDar =
×
552
          dataAccessRequestService.findByReferenceId(data.getReferenceId());
×
553
      if (Objects.nonNull(existingDar)
×
554
          && existingDar.getUserId().equals(user.getUserId())
×
555
          && existingDar.getDraft()) {
×
556
        newDar.setReferenceId(data.getReferenceId());
×
557

558
        // if dar was part of a collection, we should use the same collection.
559
        if (Objects.nonNull(existingDar.getCollectionId())) {
×
560
          newDar.setCollectionId(existingDar.getCollectionId());
×
561
        }
562
      } else {
563
        String referenceId = UUID.randomUUID().toString();
×
564
        newDar.setReferenceId(referenceId);
×
565
        data.setReferenceId(referenceId);
×
566
      }
567
    } else {
×
568
      String referenceId = UUID.randomUUID().toString();
1✔
569
      newDar.setReferenceId(referenceId);
1✔
570
      data.setReferenceId(referenceId);
1✔
571
    }
572
    newDar.setData(data);
1✔
573
    newDar.addDatasetIds(data.getDatasetIds());
1✔
574
    return newDar;
1✔
575
  }
576

577
  private void checkAuthorizedUpdateUser(User user, DataAccessRequest dar) {
578
    if (!user.getUserId().equals(dar.getUserId())) {
1✔
579
      throw new ForbiddenException("User not authorized to update this Data Access Request");
1✔
580
    }
581
    if (user.getLibraryCards().isEmpty()) {
1✔
UNCOV
582
      throw new LibraryCardRequiredException();
×
583
    }
584
  }
1✔
585

586
  private DataAccessRequest updateDarWithDocumentContents(
587
      DarDocumentType type,
588
      User user,
589
      DataAccessRequest dar,
590
      InputStream uploadInputStream,
591
      FormDataContentDisposition fileDetail) throws IOException {
592
    validateFileDetails(fileDetail);
1✔
593
    String fileName = fileDetail.getFileName();
1✔
594
    UUID id = UUID.randomUUID();
1✔
595
    BlobId blobId = gcsService.storeDocument(uploadInputStream, fileDetail.getType(), id);
1✔
596
    switch (type) {
1✔
597
      case IRB:
598
        // Delete the current document if it exists
599
        if (Objects.nonNull(dar.getData().getIrbDocumentLocation())) {
1✔
600
          deleteDarDocument(dar, dar.getData().getIrbDocumentLocation());
1✔
601
        }
602
        dar.getData().setIrbDocumentLocation(blobId.getName());
1✔
603
        dar.getData().setIrbDocumentName(fileName);
1✔
604
        break;
1✔
605
      case COLLABORATION:
606
        // Delete the current document if it exists
607
        if (Objects.nonNull(dar.getData().getCollaborationLetterLocation())) {
1✔
608
          deleteDarDocument(dar, dar.getData().getCollaborationLetterLocation());
1✔
609
        }
610
        dar.getData().setCollaborationLetterLocation(blobId.getName());
1✔
611
        dar.getData().setCollaborationLetterName(fileName);
1✔
612
        break;
1✔
613
      default:
614
        break;
615
    }
616
    return dataAccessRequestService.updateByReferenceId(user, dar);
1✔
617
  }
618

619
  private void deleteDarDocument(DataAccessRequest dar, String blobIdName) {
620
    try {
621
      gcsService.deleteDocument(blobIdName);
1✔
622
    } catch (Exception e) {
×
623
      String message = String.format(
×
624
          "Unable to delete document for DAR ID: %s; dar document location: %s",
625
          dar.getReferenceId(), blobIdName);
×
626
      logWarn(message);
×
627
    }
1✔
628
  }
1✔
629

630
  private DataAccessRequest getDarById(String referenceId) {
631
    DataAccessRequest dar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
632
    if (Objects.isNull(dar)) {
1✔
633
      throw new NotFoundException();
1✔
634
    }
635
    return dar;
1✔
636
  }
637

638
  /**
639
   * Custom handler for validating that a user can access a DAR. User will have access if ANY of
640
   * these conditions are met: If the DAR create user is the same as the Auth User, then the user
641
   * can access the resource. If the user has any of the roles in allowableRoles, then the user can
642
   * access the resource. In practice, pass in allowableRoles for users that are not the create user
643
   * (i.e. Admin) so they can also have access to the DAR.
644
   *
645
   * @param allowableRoles List of roles that would allow the user to access the resource
646
   * @param authUser       The AuthUser
647
   * @param referenceId    The referenceId of the resource.
648
   */
649
  private void validateAuthedRoleUser(final List<UserRoles> allowableRoles, AuthUser authUser,
650
      String referenceId) {
651
    DataAccessRequest dataAccessRequest = getDarById(referenceId);
1✔
652
    User user = findUserByEmail(authUser.getEmail());
1✔
653
    if (Objects.nonNull(dataAccessRequest.getUserId()) && dataAccessRequest.getUserId() > 0) {
1✔
654
      super.validateAuthedRoleUser(allowableRoles, user, dataAccessRequest.getUserId());
1✔
655
    } else {
656
      logWarn("DataAccessRequest '" + referenceId + "' has an invalid userId");
×
657
      super.validateAuthedRoleUser(allowableRoles, user, dataAccessRequest.getUserId());
×
658
    }
659
  }
1✔
660
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc