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

DataBiosphere / consent / #5727

23 Apr 2025 07:27PM UTC coverage: 79.087% (+0.07%) from 79.021%
#5727

push

web-flow
[DT-1545] Block submitted dars from being modified. (#2495)

38 of 40 new or added lines in 5 files covered. (95.0%)

10271 of 12987 relevant lines covered (79.09%)

0.79 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

587
  private DataAccessRequest updateDarWithDocumentContents(
588
      DarDocumentType type,
589
      User user,
590
      DataAccessRequest dar,
591
      InputStream uploadInputStream,
592
      FormDataContentDisposition fileDetail) throws IOException {
593
    // When we move updateDarWithDocumentContents to the service tier, we should incorporate the
594
    // code below into that method
595
    if (!dar.getDraft()) {
1✔
NEW
596
      throw new SubmittedDARCannotBeEditedException();
×
597
    }
598
    // This should be moved to service tier logic and the transactions should be coordinated
599
    validateFileDetails(fileDetail);
1✔
600
    String fileName = fileDetail.getFileName();
1✔
601
    UUID id = UUID.randomUUID();
1✔
602
    BlobId blobId = gcsService.storeDocument(uploadInputStream, fileDetail.getType(), id);
1✔
603
    switch (type) {
1✔
604
      case IRB:
605
        // Delete the current document if it exists
606
        if (Objects.nonNull(dar.getData().getIrbDocumentLocation())) {
1✔
607
          deleteDarDocument(dar, dar.getData().getIrbDocumentLocation());
1✔
608
        }
609
        dar.getData().setIrbDocumentLocation(blobId.getName());
1✔
610
        dar.getData().setIrbDocumentName(fileName);
1✔
611
        break;
1✔
612
      case COLLABORATION:
613
        // Delete the current document if it exists
614
        if (Objects.nonNull(dar.getData().getCollaborationLetterLocation())) {
1✔
615
          deleteDarDocument(dar, dar.getData().getCollaborationLetterLocation());
1✔
616
        }
617
        dar.getData().setCollaborationLetterLocation(blobId.getName());
1✔
618
        dar.getData().setCollaborationLetterName(fileName);
1✔
619
        break;
1✔
620
      default:
621
        break;
622
    }
623
    return dataAccessRequestService.updateByReferenceId(user, dar);
1✔
624
  }
625

626
  private void deleteDarDocument(DataAccessRequest dar, String blobIdName) {
627
    try {
628
      gcsService.deleteDocument(blobIdName);
1✔
629
    } catch (Exception e) {
×
630
      String message = String.format(
×
631
          "Unable to delete document for DAR ID: %s; dar document location: %s",
632
          dar.getReferenceId(), blobIdName);
×
633
      logWarn(message);
×
634
    }
1✔
635
  }
1✔
636

637
  private DataAccessRequest getDarById(String referenceId) {
638
    DataAccessRequest dar = dataAccessRequestService.findByReferenceId(referenceId);
1✔
639
    if (Objects.isNull(dar)) {
1✔
640
      throw new NotFoundException();
1✔
641
    }
642
    return dar;
1✔
643
  }
644

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

© 2026 Coveralls, Inc