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

DataBiosphere / consent / #5971

28 May 2025 01:45PM UTC coverage: 78.503% (-0.06%) from 78.558%
#5971

push

web-flow
DT-1662: Ensure single Library Card per User (#2536)

83 of 86 new or added lines in 9 files covered. (96.51%)

4 existing lines in 3 files now uncovered.

10028 of 12774 relevant lines covered (78.5%)

0.79 hits per line

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

97.8
/src/main/java/org/broadinstitute/consent/http/resources/DaaResource.java
1
package org.broadinstitute.consent.http.resources;
2

3
import com.google.inject.Inject;
4
import io.dropwizard.auth.Auth;
5
import jakarta.annotation.security.PermitAll;
6
import jakarta.annotation.security.RolesAllowed;
7
import jakarta.ws.rs.BadRequestException;
8
import jakarta.ws.rs.Consumes;
9
import jakarta.ws.rs.DELETE;
10
import jakarta.ws.rs.GET;
11
import jakarta.ws.rs.POST;
12
import jakarta.ws.rs.PUT;
13
import jakarta.ws.rs.Path;
14
import jakarta.ws.rs.PathParam;
15
import jakarta.ws.rs.Produces;
16
import jakarta.ws.rs.core.Context;
17
import jakarta.ws.rs.core.MediaType;
18
import jakarta.ws.rs.core.Response;
19
import jakarta.ws.rs.core.Response.Status;
20
import jakarta.ws.rs.core.StreamingOutput;
21
import jakarta.ws.rs.core.UriInfo;
22
import java.io.InputStream;
23
import java.net.URI;
24
import java.util.List;
25
import java.util.Objects;
26
import java.util.Optional;
27
import org.broadinstitute.consent.http.enumeration.UserRoles;
28
import org.broadinstitute.consent.http.models.AuthUser;
29
import org.broadinstitute.consent.http.models.Dac;
30
import org.broadinstitute.consent.http.models.DataAccessAgreement;
31
import org.broadinstitute.consent.http.models.LibraryCard;
32
import org.broadinstitute.consent.http.models.User;
33
import org.broadinstitute.consent.http.models.UserRole;
34
import org.broadinstitute.consent.http.service.DaaService;
35
import org.broadinstitute.consent.http.service.DacService;
36
import org.broadinstitute.consent.http.service.LibraryCardService;
37
import org.broadinstitute.consent.http.service.UserService;
38
import org.broadinstitute.consent.http.util.ConsentLogger;
39
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
40
import org.glassfish.jersey.media.multipart.FormDataParam;
41

42
@Path("api/daa")
43
public class DaaResource extends Resource implements ConsentLogger {
44

45
  private final DaaService daaService;
46
  private final DacService dacService;
47
  private final UserService userService;
48
  private final LibraryCardService libraryCardService;
49

50
  @Inject
51
  public DaaResource(DaaService daaService, DacService dacService, UserService userService,
52
      LibraryCardService libraryCardService) {
1✔
53
    this.daaService = daaService;
1✔
54
    this.dacService = dacService;
1✔
55
    this.userService = userService;
1✔
56
    this.libraryCardService = libraryCardService;
1✔
57
  }
1✔
58

59
  @POST
60
  @Consumes(MediaType.MULTIPART_FORM_DATA)
61
  @Produces(MediaType.APPLICATION_JSON)
62
  @RolesAllowed({ADMIN, CHAIRPERSON})
63
  @Path("/dac/{dacId}")
64
  public Response createDaaForDac(
65
      @Context UriInfo info,
66
      @Auth AuthUser authUser,
67
      @PathParam("dacId") Integer dacId,
68
      @FormDataParam("file") InputStream uploadInputStream,
69
      @FormDataParam("file") FormDataContentDisposition fileDetail) {
70
    try {
71
      validateFileDetails(fileDetail);
1✔
72
      dacService.findById(dacId);
1✔
73
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
74
      // Assert that the user has the correct DAC permissions to add a DAA for the provided DacId.
75
      // Admins can add a DAA with any DAC, but chairpersons can only add DAAs for DACs they are a
76
      // chairperson for.
77
      if (!user.hasUserRole(UserRoles.ADMIN)) {
1✔
78
        List<Integer> dacIds = user.getRoles().stream().map(UserRole::getDacId).toList();
1✔
79
        if (!dacIds.contains(dacId)) {
1✔
80
          return Response.status(Status.FORBIDDEN).build();
1✔
81
        }
82
      }
83
      DataAccessAgreement daa = daaService.createDaaWithFso(user.getUserId(), dacId,
1✔
84
          uploadInputStream, fileDetail);
85
      URI uri = info.getBaseUriBuilder()
1✔
86
          // This will be the GET endpoint for the created DAA
87
          .replacePath("api/daa/{daaId}")
1✔
88
          .build(daa.getDaaId());
1✔
89
      return Response.created(uri).entity(daa).build();
1✔
90
    } catch (Exception e) {
1✔
91
      return createExceptionResponse(e);
1✔
92
    }
93
  }
94

95
  @PUT
96
  @Produces(MediaType.APPLICATION_JSON)
97
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
98
  @Path("{daaId}/{userId}")
99
  public Response createLibraryCardDaaRelation(
100
      @Context UriInfo info,
101
      @Auth AuthUser authUser,
102
      @PathParam("daaId") Integer daaId,
103
      @PathParam("userId") Integer userId) {
104
    try {
105
      User authedUser = userService.findUserByEmail(authUser.getEmail());
1✔
106
      int authedUserInstitutionId = authedUser.getInstitutionId();
1✔
107
      User user = userService.findUserById(userId);
1✔
108
      int userInstitutionId = user.getInstitutionId();
1✔
109
      // Assert that the user has the correct institution permissions to add a DAA-LC relationship.
110
      // Admins can add a DAA with any DAC, but signing officials can only create relationships for
111
      // library cards associated with the same institution they are associated with.
112
      if (!authedUser.hasUserRole(UserRoles.ADMIN) && !authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL)) {
1✔
113
        return Response.status(Status.FORBIDDEN).build();
1✔
114
      } else if (authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL) && authedUserInstitutionId != userInstitutionId) {
1✔
115
          return Response.status(Status.FORBIDDEN).build();
1✔
116
      }
117
      LibraryCard libraryCard = user.getLibraryCard() == null ?
1✔
118
          libraryCardService.createLibraryCardForSigningOfficial(user, authedUser) :
1✔
119
          user.getLibraryCard();
1✔
120
      libraryCardService.addDaaToLibraryCard(libraryCard.getId(), daaId);
1✔
121
      URI uri = info.getBaseUriBuilder()
1✔
122
          .replacePath("api/libraryCards/{libraryCardId}")
1✔
123
          .build(libraryCard.getId());
1✔
124
      return Response.ok().location(uri).entity(libraryCard).build();
1✔
125
    } catch (Exception e) {
1✔
126
      return createExceptionResponse(e);
1✔
127
    }
128
  }
129

130
  @GET
131
  @Produces(MediaType.APPLICATION_JSON)
132
  @PermitAll
133
  public Response findAll() {
134
    try {
135
      List<DataAccessAgreement> daas = daaService.findAll();
1✔
136
      return Response.ok(daas).build();
1✔
137
    } catch (Exception e) {
×
138
      return createExceptionResponse(e);
×
139
    }
140
  }
141

142
  @GET
143
  @Produces(MediaType.APPLICATION_JSON)
144
  @RolesAllowed({ADMIN, MEMBER, CHAIRPERSON, RESEARCHER})
145
  @Path("{daaId}")
146
  public Response findById(
147
      @Auth AuthUser authUser,
148
      @PathParam("daaId") Integer daaId) {
149
    try {
150
      DataAccessAgreement daa = daaService.findById(daaId);
1✔
151
      return Response.ok(daa).build();
1✔
152
    } catch (Exception e) {
1✔
153
      return createExceptionResponse(e);
1✔
154
    }
155
  }
156

157
  @GET
158
  @PermitAll
159
  @Path("{daaId}/file")
160
  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
161
  public Response findFileById(
162
      @PathParam("daaId") Integer daaId) {
163
    try {
164
      InputStream daa = daaService.findFileById(daaId);
1✔
165
      StreamingOutput stream = createStreamingOutput(daa);
1✔
166
      DataAccessAgreement daa2 = daaService.findById(daaId);
1✔
167
      String fileName = daa2.getFile().getFileName();
1✔
168
      return Response.ok(stream)
1✔
169
          .header("Content-Disposition", "attachment; filename=" + fileName)
1✔
170
          .build();
1✔
171
    } catch (Exception e) {
1✔
172
      return createExceptionResponse(e);
1✔
173
    }
174
  }
175

176
  @DELETE
177
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
178
  @Path("{daaId}/{userId}")
179
  public Response deleteDaaForUser(
180
      @Auth AuthUser authUser,
181
      @PathParam("daaId") Integer daaId,
182
      @PathParam("userId") Integer userId) {
183
    try {
184
      User user = userService.findUserById(userId);
1✔
185
      if (user.getLibraryCard() != null) {
1✔
186
        libraryCardService.removeDaaFromLibraryCard(user.getLibraryCard().getId(), daaId);
1✔
187
      }
188
      return Response.ok().build();
1✔
189
    } catch (Exception e) {
1✔
190
      return createExceptionResponse(e);
1✔
191
    }
192
  }
193

194
  @DELETE
195
  @Path("{daaId}")
196
  @Produces("application/json")
197
  @RolesAllowed({ADMIN})
198
  public Response adminDeleteDaa(
199
      @Auth AuthUser authUser,
200
      @PathParam("daaId") Integer daaId) {
201
    try {
202
      daaService.findById(daaId);
1✔
203
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
204
      daaService.deleteDaa(daaId);
1✔
205
      return Response.ok().build();
1✔
206
    } catch (Exception e) {
1✔
207
      return createExceptionResponse(e);
1✔
208
    }
209
  }
210

211
  @POST
212
  @PermitAll
213
  @Path("/request/{daaId}")
214
  public Response sendDaaRequestMessage(
215
      @Auth AuthUser authUser,
216
      @PathParam("daaId") Integer daaId) {
217
    try {
218
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
219
      if (user.getInstitutionId() == null) {
1✔
220
        throw new BadRequestException("This user has not set their institution: " + user.getDisplayName());
1✔
221
      }
222
      if (user.getLibraryCard() == null) {
1✔
NEW
223
        throw new BadRequestException("This user does not have a library card: " + user.getDisplayName());
×
224
      }
225
      if (user.getLibraryCard().getDaaIds().contains(daaId)) {
1✔
UNCOV
226
        throw new IllegalArgumentException("User already has this DAA associated with their Library Card");
×
227
      }
228
      daaService.sendDaaRequestEmails(user, daaId);
1✔
229
      return Response.ok().build();
1✔
230
    } catch (Exception e) {
1✔
231
      return createExceptionResponse(e);
1✔
232
    }
233
  }
234

235
  @POST
236
  @Consumes(MediaType.APPLICATION_JSON)
237
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
238
  @Path("/bulk/{daaId}")
239
  public Response bulkAddUsersToDaa(
240
      @Auth AuthUser authUser,
241
      @PathParam("daaId") Integer daaId,
242
      String json) {
243
    try {
244
      User authedUser = userService.findUserByEmail(authUser.getEmail());
1✔
245
      List<User> users = userService.findUsersInJsonArray(json, "users");
1✔
246
      if (authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL) && !authedUser.hasUserRole(UserRoles.ADMIN)) {
1✔
247
        for (User user : users) {
1✔
248
          if (!Objects.equals(authedUser.getInstitutionId(), user.getInstitutionId())) {
1✔
249
            return Response.status(Status.FORBIDDEN).build();
1✔
250
          }
251
        }
1✔
252
      }
253
      daaService.findById(daaId);
1✔
254
      for (User user : users) {
1✔
255
        libraryCardService.addDaaToUserLibraryCard(user, authedUser, daaId);
1✔
256
      }
1✔
257
      return Response.ok().build();
1✔
258
    } catch (Exception e) {
1✔
259
      return createExceptionResponse(e);
1✔
260
    }
261
  }
262

263
  @DELETE
264
  @Consumes(MediaType.APPLICATION_JSON)
265
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
266
  @Path("/bulk/{daaId}")
267
  public Response bulkRemoveUsersFromDaa(
268
      @Auth AuthUser authUser,
269
      @PathParam("daaId") Integer daaId,
270
      String json) {
271
    try {
272
      User authedUser = userService.findUserByEmail(authUser.getEmail());
1✔
273
      List<User> users = userService.findUsersInJsonArray(json, "users");
1✔
274
      if (authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL) && !authedUser.hasUserRole(UserRoles.ADMIN)) {
1✔
275
        for (User user : users) {
1✔
276
          if (!Objects.equals(authedUser.getInstitutionId(), user.getInstitutionId())) {
1✔
277
            return Response.status(Status.FORBIDDEN).build();
1✔
278
          }
279
        }
1✔
280
      }
281
      daaService.findById(daaId);
1✔
282
      for (User user : users) {
1✔
283
        libraryCardService.removeDaaFromUserLibraryCard(user, daaId);
1✔
284
      }
1✔
285
      return Response.ok().build();
1✔
286
    } catch (Exception e) {
1✔
287
      return createExceptionResponse(e);
1✔
288
    }
289
  }
290

291
  @POST
292
  @Consumes(MediaType.APPLICATION_JSON)
293
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
294
  @Path("/bulk/user/{userId}")
295
  public Response bulkAddDAAsToUser(
296
      @Auth AuthUser authUser,
297
      @PathParam("userId") Integer userId,
298
      String json) {
299
    try {
300
      User authedUser = userService.findUserByEmail(authUser.getEmail());
1✔
301
      User user = userService.findUserById(userId);
1✔
302
      if (authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL) && !Objects.equals(authedUser.getInstitutionId(), user.getInstitutionId())) {
1✔
303
        return Response.status(Status.FORBIDDEN).build();
1✔
304
      }
305
      List<DataAccessAgreement> daaList = daaService.findDAAsInJsonArray(json, "daaList");
1✔
306
      for (DataAccessAgreement daa : daaList) {
1✔
307
        libraryCardService.addDaaToUserLibraryCard(user, authedUser, daa.getDaaId());
1✔
308
      }
1✔
309
      return Response.ok().build();
1✔
310
    } catch (Exception e) {
1✔
311
      return createExceptionResponse(e);
1✔
312
    }
313
  }
314

315
  @DELETE
316
  @Consumes(MediaType.APPLICATION_JSON)
317
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
318
  @Path("/bulk/user/{userId}")
319
  public Response bulkRemoveDAAsFromUser(
320
      @Auth AuthUser authUser,
321
      @PathParam("userId") Integer userId,
322
      String json) {
323
    try {
324
      User authedUser = userService.findUserByEmail(authUser.getEmail());
1✔
325
      User user = userService.findUserById(userId);
1✔
326
      if (authedUser.hasUserRole(UserRoles.SIGNINGOFFICIAL) && !Objects.equals(authedUser.getInstitutionId(), user.getInstitutionId())) {
1✔
327
        return Response.status(Status.FORBIDDEN).build();
1✔
328
      }
329
      List<DataAccessAgreement> daaList = daaService.findDAAsInJsonArray(json, "daaList");
1✔
330
      for (DataAccessAgreement daa : daaList) {
1✔
331
        libraryCardService.removeDaaFromUserLibraryCard(user, daa.getDaaId());
1✔
332
      }
1✔
333
      return Response.ok().build();
1✔
334
    } catch (Exception e) {
1✔
335
      return createExceptionResponse(e);
1✔
336
    }
337
  }
338

339
  @PUT
340
  @Produces(MediaType.APPLICATION_JSON)
341
  @RolesAllowed({ADMIN, CHAIRPERSON})
342
  @Path("/{daaId}/dac/{dacId}")
343
  public Response modifyDacDaaRelationship(
344
      @Auth AuthUser authUser,
345
      @PathParam("daaId") Integer daaId,
346
      @PathParam("dacId") Integer dacId) {
347
    try {
348
      dacService.findById(dacId);
1✔
349
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
350
      // Assert that the user has the correct DAC permissions to add a DAC to a DAA for the provided DacId.
351
      // Admins can add a DAC to a DAA with any DAC, but chairpersons can only add DACs to DAAs for DACs they are a
352
      // chairperson for.
353
      if (!user.hasUserRole(UserRoles.ADMIN)) {
1✔
354
        if (user.getRoles()
1✔
355
            .stream()
1✔
356
            .filter(r -> r.getRoleId().equals(UserRoles.Chairperson().getRoleId()))
1✔
357
            .map(UserRole::getDacId)
1✔
358
            .noneMatch(dacId::equals)) {
1✔
359
          return Response.status(Status.FORBIDDEN).build();
1✔
360
        }
361
      }
362
      DataAccessAgreement daa = daaService.findById(daaId);
1✔
363
      Optional<Dac> matchingDac = Optional.empty();
1✔
364
      if (daa.getDacs() != null) {
1✔
365
        matchingDac = daa.getDacs().stream()
1✔
366
            .filter(dac -> Objects.equals(dac.getDacId(), dacId))
1✔
367
            .findFirst();
1✔
368
      }
369
      if (matchingDac.isEmpty()) {
1✔
370
        daaService.addDacToDaa(dacId,daaId);
1✔
371
      }
372
      DataAccessAgreement updatedDaa = daaService.findById(daaId);
1✔
373
      return Response.ok().entity(updatedDaa).build();
1✔
374
    } catch (Exception e) {
1✔
375
      return createExceptionResponse(e);
1✔
376
    }
377
  }
378

379
  @DELETE
380
  @Consumes(MediaType.APPLICATION_JSON)
381
  @RolesAllowed({ADMIN, CHAIRPERSON})
382
  @Path("/{daaId}/dac/{dacId}")
383
  public Response removeDacDaaRelationship(
384
      @Auth AuthUser authUser,
385
      @PathParam("daaId") Integer daaId,
386
      @PathParam("dacId") Integer dacId) {
387
    try {
388
      dacService.findById(dacId);
1✔
389
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
390
      // Assert that the user has the correct DAC permissions to add a DAC to a DAA for the provided DacId.
391
      // Admins can add a DAC to a DAA with any DAC, but chairpersons can only add DACs to DAAs for DACs they are a
392
      // chairperson for.
393
      if (!user.hasUserRole(UserRoles.ADMIN)) {
1✔
394
        List<Integer> matchedChairpersonDacIds = user
1✔
395
            .getRoles()
1✔
396
            .stream()
1✔
397
            .filter(r -> r.getRoleId().equals(UserRoles.Chairperson().getRoleId()))
1✔
398
            .map(UserRole::getDacId)
1✔
399
            .filter(id -> Objects.equals(id, dacId))
1✔
400
            .toList();
1✔
401
        if (matchedChairpersonDacIds.isEmpty()) {
1✔
402
          return Response.status(Status.FORBIDDEN).build();
1✔
403
        }
404
      }
405
      DataAccessAgreement daa = daaService.findById(daaId);
1✔
406
      Optional<Dac> matchingDac = Optional.empty();
1✔
407
      if (daa.getDacs() != null) {
1✔
408
        matchingDac = daa.getDacs().stream()
1✔
409
            .filter(dac -> Objects.equals(dac.getDacId(), dacId))
1✔
410
            .findFirst();
1✔
411
      }
412
      if (matchingDac.isEmpty()) {
1✔
413
        throw new BadRequestException("The given DAC is not associated with the provided DAA.");
1✔
414
      } else {
415
        daaService.removeDacFromDaa(dacId, daaId);
1✔
416
      }
417
      DataAccessAgreement updatedDaa = daaService.findById(daaId);
1✔
418
      return Response.ok().entity(updatedDaa).build();
1✔
419
    } catch (Exception e) {
1✔
420
      return createExceptionResponse(e);
1✔
421
    }
422
  }
423

424
  @POST
425
  @RolesAllowed({ADMIN, CHAIRPERSON})
426
  @Path("{dacId}/updated/{oldDaaId}/{newDaaName}")
427
  public Response sendNewDaaMessage(
428
      @Auth AuthUser authUser,
429
      @PathParam("dacId") Integer dacId,
430
      @PathParam("oldDaaId") Integer oldDaaId,
431
      @PathParam("newDaaName") String newDaaName
432
      ) {
433
    try {
434
      daaService.findById(oldDaaId);
1✔
435
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
436
      Dac dac = dacService.findById(dacId);
1✔
437
      String dacName = dac.getName();
1✔
438
      daaService.sendNewDaaEmails(user, oldDaaId, dacName, newDaaName);
1✔
439
      return Response.ok().build();
1✔
440
    } catch (Exception e) {
1✔
441
      return createExceptionResponse(e);
1✔
442
    }
443
  }
444
}
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