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

DataBiosphere / consent / #5676

17 Apr 2025 12:57PM UTC coverage: 79.034% (-0.04%) from 79.075%
#5676

push

web-flow
[DT-1502] Enforce Library card rule on all DAR creation; return specific Error text when this rule is being enforced. (#2481)

82 of 92 new or added lines in 8 files covered. (89.13%)

1 existing line in 1 file now uncovered.

10261 of 12983 relevant lines covered (79.03%)

0.79 hits per line

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

84.48
/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.exceptions.NIHComplianceRuleException;
40
import org.broadinstitute.consent.http.exceptions.UnprocessableEntityException;
41
import org.broadinstitute.consent.http.models.AuthUser;
42
import org.broadinstitute.consent.http.models.DataAccessAgreement;
43
import org.broadinstitute.consent.http.models.DataAccessRequest;
44
import org.broadinstitute.consent.http.models.DataAccessRequestData;
45
import org.broadinstitute.consent.http.models.DataUse;
46
import org.broadinstitute.consent.http.models.Dataset;
47
import org.broadinstitute.consent.http.models.Error;
48
import org.broadinstitute.consent.http.models.User;
49
import org.broadinstitute.consent.http.service.DaaService;
50
import org.broadinstitute.consent.http.service.DataAccessRequestService;
51
import org.broadinstitute.consent.http.service.DatasetService;
52
import org.broadinstitute.consent.http.service.EmailService;
53
import org.broadinstitute.consent.http.service.MatchService;
54
import org.broadinstitute.consent.http.service.UserService;
55
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
56
import org.glassfish.jersey.media.multipart.FormDataParam;
57

58
@Path("api/dar")
59
public class DataAccessRequestResource extends Resource {
60

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

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

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

102
  @POST
103
  @Consumes("application/json")
104
  @Produces("application/json")
105
  @RolesAllowed(RESEARCHER)
106
  @Path("/v2")
107
  public Response createDataAccessRequest(
108
      @Auth AuthUser authUser, @Context UriInfo info, String dar) {
109
    try {
110
      User user = findUserByEmail(authUser.getEmail());
1✔
111
      if (user.getLibraryCards().isEmpty()) {
1✔
112
        throw new NIHComplianceRuleException();
1✔
113
      }
114

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

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

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

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

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

231
  @GET
232
  @Produces("application/json")
233
  @Path("/v2/draft")
234
  @RolesAllowed(RESEARCHER)
235
  public Response getDraftDataAccessRequests(@Auth AuthUser authUser) {
236
    try {
237
      User user = findUserByEmail(authUser.getEmail());
1✔
238
      List<DataAccessRequest> draftDars = dataAccessRequestService.findAllDraftDataAccessRequestsByUser(
1✔
239
          user.getUserId());
1✔
240
      return Response.ok().entity(draftDars).build();
1✔
241
    } catch (Exception e) {
1✔
242
      return createExceptionResponse(e);
1✔
243
    }
244
  }
245

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

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

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

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

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

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

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

408
  @POST
409
  @Consumes(MediaType.MULTIPART_FORM_DATA)
410
  @Produces(MediaType.APPLICATION_JSON)
411
  @Path("/v2/progress_report/{parentReferenceId}")
412
  @RolesAllowed({RESEARCHER})
413
  public Response postProgressReport(
414
      @Auth AuthUser authUser,
415
      @PathParam("parentReferenceId") String parentReferenceId,
416
      @FormDataParam("dar") String dar,
417
      @FormDataParam("collaboratorRequiredFile") InputStream collabInputStream,
418
      @FormDataParam("collaboratorRequiredFile") FormDataContentDisposition collabFileDetails,
419
      @FormDataParam("ethicsApprovalRequiredFile") InputStream ethicsInputStream,
420
      @FormDataParam("ethicsApprovalRequiredFile") FormDataContentDisposition ethicsFileDetails) {
421
    User user = userService.findUserByEmail(authUser.getEmail());
1✔
422
    DataAccessRequest parentDar = dataAccessRequestService.findByReferenceId(parentReferenceId);
1✔
423
    try {
424
      checkAuthorizedUpdateUser(user, parentDar);
1✔
425
    } catch (Exception e) {
1✔
426
      return createExceptionResponse(e);
1✔
427
    }
1✔
428

429
    DataAccessRequest payload = populateDarFromJsonString(user, dar);
1✔
430
    DataAccessRequest childDar = dataAccessRequestService.createDataAccessRequest(user, payload);
1✔
431

432
    for (Integer datasetId : childDar.getDatasetIds()) {
1✔
433
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
434
      if (dataset == null) {
1✔
435
        throw new NotFoundException("Dataset " + datasetId + " not found");
1✔
436
      }
437
      DataUse dataUse = dataset.getDataUse();
1✔
438
      if (dataUse == null || dataUse.getCollaboratorRequired() == null
1✔
439
          || dataUse.getEthicsApprovalRequired() == null) {
1✔
440
        throw new BadRequestException("Dataset " + datasetId + " is missing data use(s)");
1✔
441
      }
442
      if (dataUse.getCollaboratorRequired()) {
1✔
443
        String parentCollabLocation = parentDar.getData().getCollaborationLetterLocation();
1✔
444
        if ((collabFileDetails == null || collabFileDetails.getSize() <= 0)
1✔
445
            && Strings.isNullOrEmpty(parentCollabLocation)) {
1✔
446
          throw new BadRequestException("Collaboration document is required");
1✔
447
        }
448
        try {
449
          childDar = updateDarWithDocumentContents(DarDocumentType.COLLABORATION, user, childDar,
1✔
450
              collabInputStream, collabFileDetails);
451
        } catch (IOException e) {
×
452
          return createExceptionResponse(e);
×
453
        }
1✔
454
      }
455
      if (dataUse.getEthicsApprovalRequired()) {
1✔
456
        String parentEthicsLocation = parentDar.getData().getIrbDocumentLocation();
1✔
457
        if ((ethicsFileDetails == null || ethicsFileDetails.getSize() <= 0)
1✔
458
            && Strings.isNullOrEmpty(parentEthicsLocation)) {
1✔
459
          throw new BadRequestException("Ethics approval document is required");
×
460
        }
461
        try {
462
          childDar = updateDarWithDocumentContents(DarDocumentType.IRB, user, childDar,
1✔
463
              ethicsInputStream, ethicsFileDetails);
464
        } catch (IOException e) {
×
465
          return createExceptionResponse(e);
×
466
        }
1✔
467
      }
468
    }
1✔
469

470
    return Response.ok(childDar.convertToSimplifiedDar()).build();
1✔
471
  }
472

473
  @GET
474
  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
475
  @Path("/v2/{referenceId}/collaborationDocument")
476
  @RolesAllowed({ADMIN, CHAIRPERSON, MEMBER, RESEARCHER})
477
  public Response getCollaborationDocument(
478
      @Auth AuthUser authUser,
479
      @PathParam("referenceId") String referenceId) {
480
    try {
481
      DataAccessRequest dar = getDarById(referenceId);
1✔
482
      validateAuthedRoleUser(
1✔
483
          Stream.of(UserRoles.ADMIN, UserRoles.CHAIRPERSON, UserRoles.MEMBER)
1✔
484
              .collect(Collectors.toList()),
1✔
485
          authUser, referenceId);
486
      if (dar.getData() != null &&
1✔
487
          StringUtils.isNotEmpty(dar.getData().getCollaborationLetterLocation()) &&
1✔
488
          StringUtils.isNotEmpty(dar.getData().getCollaborationLetterName())
1✔
489
      ) {
490
        String blobIdName = dar.getData().getCollaborationLetterLocation();
1✔
491
        String fileName = dar.getData().getCollaborationLetterName();
1✔
492
        InputStream is = gcsService.getDocument(blobIdName);
1✔
493
        StreamingOutput stream = createStreamingOutput(is);
1✔
494
        return Response.ok(stream)
1✔
495
            .header("Content-Disposition", "attachment; filename=" + fileName)
1✔
496
            .build();
1✔
497
      }
498
      throw new NotFoundException();
1✔
499
    } catch (Exception e) {
1✔
500
      return createExceptionResponse(e);
1✔
501
    }
502
  }
503

504
  @POST
505
  @Consumes(MediaType.MULTIPART_FORM_DATA)
506
  @Produces(MediaType.APPLICATION_JSON)
507
  @Path("/v2/{referenceId}/collaborationDocument")
508
  @RolesAllowed({RESEARCHER})
509
  public Response uploadCollaborationDocument(
510
      @Auth AuthUser authUser,
511
      @PathParam("referenceId") String referenceId,
512
      @FormDataParam("file") InputStream uploadInputStream,
513
      @FormDataParam("file") FormDataContentDisposition fileDetail) {
514
    try {
515
      User user = findUserByEmail(authUser.getEmail());
1✔
516
      DataAccessRequest dar = getDarById(referenceId);
1✔
517
      checkAuthorizedUpdateUser(user, dar);
1✔
518
      DataAccessRequest updatedDar = updateDarWithDocumentContents(DarDocumentType.COLLABORATION,
1✔
519
          user, dar, uploadInputStream, fileDetail);
520
      return Response.ok(updatedDar.convertToSimplifiedDar()).build();
1✔
521
    } catch (Exception e) {
1✔
522
      return createExceptionResponse(e);
1✔
523
    }
524
  }
525

526
  @DELETE
527
  @Path("/v2/{referenceId}")
528
  @Produces("application/json")
529
  @RolesAllowed({ADMIN, RESEARCHER})
530
  public Response deleteDar(@Auth AuthUser authUser, @PathParam("referenceId") String referenceId) {
531
    validateAuthedRoleUser(Collections.singletonList(UserRoles.ADMIN), authUser, referenceId);
×
532
    try {
533
      User user = findUserByEmail(authUser.getEmail());
×
534
      dataAccessRequestService.deleteByReferenceId(user, referenceId);
×
535
      return Response.ok().build();
×
536
    } catch (Exception e) {
×
537
      return createExceptionResponse(e);
×
538
    }
539
  }
540

541
  private User findUserByEmail(String email) {
542
    User user = userService.findUserByEmail(email);
1✔
543
    if (user == null) {
1✔
544
      throw new NotFoundException("Unable to find User with the provided email: " + email);
×
545
    }
546
    return user;
1✔
547
  }
548

549
  private DataAccessRequest populateDarFromJsonString(User user, String json) {
550
    DataAccessRequest newDar = new DataAccessRequest();
1✔
551
    DataAccessRequestData data;
552
    try {
553
      data = DataAccessRequestData.fromString(json);
1✔
554
    } catch (Exception e) {
1✔
555
      throw new BadRequestException("Unable to parse DAR from JSON string");
1✔
556
    }
1✔
557
    if (Objects.isNull(data)) {
1✔
558
      data = new DataAccessRequestData();
1✔
559
    }
560
    // When posting a submitted dar, there are two cases:
561
    // 1. those that existed previously as a draft dar
562
    // 2. those that are brand new
563
    // Validate the provided referenceId with the authenticated user and draft status
564
    // Those that do not validate are considered a brand new dar
565
    if (Objects.nonNull(data.getReferenceId())) {
1✔
566
      DataAccessRequest existingDar =
×
567
          dataAccessRequestService.findByReferenceId(data.getReferenceId());
×
568
      if (Objects.nonNull(existingDar)
×
569
          && existingDar.getUserId().equals(user.getUserId())
×
570
          && existingDar.getDraft()) {
×
571
        newDar.setReferenceId(data.getReferenceId());
×
572

573
        // if dar was part of a collection, we should use the same collection.
574
        if (Objects.nonNull(existingDar.getCollectionId())) {
×
575
          newDar.setCollectionId(existingDar.getCollectionId());
×
576
        }
577
      } else {
578
        String referenceId = UUID.randomUUID().toString();
×
579
        newDar.setReferenceId(referenceId);
×
580
        data.setReferenceId(referenceId);
×
581
      }
582
    } else {
×
583
      String referenceId = UUID.randomUUID().toString();
1✔
584
      newDar.setReferenceId(referenceId);
1✔
585
      data.setReferenceId(referenceId);
1✔
586
    }
587
    newDar.setData(data);
1✔
588
    newDar.addDatasetIds(data.getDatasetIds());
1✔
589
    return newDar;
1✔
590
  }
591

592
  private void checkAuthorizedUpdateUser(User user, DataAccessRequest dar) {
593
    if (!user.getUserId().equals(dar.getUserId())) {
1✔
594
      throw new ForbiddenException("User not authorized to update this Data Access Request");
1✔
595
    }
596
    if (user.getLibraryCards().isEmpty()) {
1✔
597
      throw new LibraryCardRequiredException();
1✔
598
    }
599
  }
1✔
600

601
  private DataAccessRequest updateDarWithDocumentContents(
602
      DarDocumentType type,
603
      User user,
604
      DataAccessRequest dar,
605
      InputStream uploadInputStream,
606
      FormDataContentDisposition fileDetail) throws IOException {
607
    validateFileDetails(fileDetail);
1✔
608
    String fileName = fileDetail.getFileName();
1✔
609
    UUID id = UUID.randomUUID();
1✔
610
    BlobId blobId = gcsService.storeDocument(uploadInputStream, fileDetail.getType(), id);
1✔
611
    switch (type) {
1✔
612
      case IRB:
613
        // Delete the current document if it exists
614
        if (Objects.nonNull(dar.getData().getIrbDocumentLocation())) {
1✔
615
          deleteDarDocument(dar, dar.getData().getIrbDocumentLocation());
1✔
616
        }
617
        dar.getData().setIrbDocumentLocation(blobId.getName());
1✔
618
        dar.getData().setIrbDocumentName(fileName);
1✔
619
        break;
1✔
620
      case COLLABORATION:
621
        // Delete the current document if it exists
622
        if (Objects.nonNull(dar.getData().getCollaborationLetterLocation())) {
1✔
623
          deleteDarDocument(dar, dar.getData().getCollaborationLetterLocation());
1✔
624
        }
625
        dar.getData().setCollaborationLetterLocation(blobId.getName());
1✔
626
        dar.getData().setCollaborationLetterName(fileName);
1✔
627
        break;
1✔
628
      default:
629
        break;
630
    }
631
    return dataAccessRequestService.updateByReferenceId(user, dar);
1✔
632
  }
633

634
  private void deleteDarDocument(DataAccessRequest dar, String blobIdName) {
635
    try {
636
      gcsService.deleteDocument(blobIdName);
1✔
637
    } catch (Exception e) {
×
638
      String message = String.format(
×
639
          "Unable to delete document for DAR ID: %s; dar document location: %s",
640
          dar.getReferenceId(), blobIdName);
×
641
      logWarn(message);
×
642
    }
1✔
643
  }
1✔
644

645
  private DataAccessRequest getDarById(String referenceId) {
646
    DataAccessRequest dar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
647
    if (Objects.isNull(dar)) {
1✔
648
      throw new NotFoundException();
1✔
649
    }
650
    return dar;
1✔
651
  }
652

653
  /**
654
   * Custom handler for validating that a user can access a DAR. User will have access if ANY of
655
   * these conditions are met: If the DAR create user is the same as the Auth User, then the user
656
   * can access the resource. If the user has any of the roles in allowableRoles, then the user can
657
   * access the resource. In practice, pass in allowableRoles for users that are not the create user
658
   * (i.e. Admin) so they can also have access to the DAR.
659
   *
660
   * @param allowableRoles List of roles that would allow the user to access the resource
661
   * @param authUser       The AuthUser
662
   * @param referenceId    The referenceId of the resource.
663
   */
664
  private void validateAuthedRoleUser(final List<UserRoles> allowableRoles, AuthUser authUser,
665
      String referenceId) {
666
    DataAccessRequest dataAccessRequest = getDarById(referenceId);
1✔
667
    User user = findUserByEmail(authUser.getEmail());
1✔
668
    if (Objects.nonNull(dataAccessRequest.getUserId()) && dataAccessRequest.getUserId() > 0) {
1✔
669
      super.validateAuthedRoleUser(allowableRoles, user, dataAccessRequest.getUserId());
1✔
670
    } else {
671
      logWarn("DataAccessRequest '" + referenceId + "' has an invalid userId");
×
672
      super.validateAuthedRoleUser(allowableRoles, user, dataAccessRequest.getUserId());
×
673
    }
674
  }
1✔
675
}
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