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

DataBiosphere / consent / #6185

08 Jul 2025 07:15PM UTC coverage: 80.365% (+0.9%) from 79.433%
#6185

push

web-flow
DT-247: Remove/replace all usages of DatasetDTO with Dataset (#2602)

6 of 13 new or added lines in 3 files covered. (46.15%)

1 existing line in 1 file now uncovered.

10384 of 12921 relevant lines covered (80.37%)

0.8 hits per line

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

91.96
/src/main/java/org/broadinstitute/consent/http/resources/UserResource.java
1
package org.broadinstitute.consent.http.resources;
2

3

4
import com.codahale.metrics.annotation.Timed;
5
import com.google.api.client.http.HttpStatusCodes;
6
import com.google.gson.Gson;
7
import com.google.gson.JsonObject;
8
import com.google.gson.reflect.TypeToken;
9
import com.google.inject.Inject;
10
import io.dropwizard.auth.Auth;
11
import jakarta.annotation.security.PermitAll;
12
import jakarta.annotation.security.RolesAllowed;
13
import jakarta.ws.rs.BadRequestException;
14
import jakarta.ws.rs.Consumes;
15
import jakarta.ws.rs.DELETE;
16
import jakarta.ws.rs.ForbiddenException;
17
import jakarta.ws.rs.GET;
18
import jakarta.ws.rs.NotFoundException;
19
import jakarta.ws.rs.POST;
20
import jakarta.ws.rs.PUT;
21
import jakarta.ws.rs.Path;
22
import jakarta.ws.rs.PathParam;
23
import jakarta.ws.rs.Produces;
24
import jakarta.ws.rs.core.Context;
25
import jakarta.ws.rs.core.MediaType;
26
import jakarta.ws.rs.core.Response;
27
import jakarta.ws.rs.core.UriInfo;
28
import java.lang.reflect.Type;
29
import java.net.URI;
30
import java.util.ArrayList;
31
import java.util.Collections;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.Objects;
35
import java.util.stream.Collectors;
36
import org.broadinstitute.consent.http.enumeration.UserRoles;
37
import org.broadinstitute.consent.http.models.Acknowledgement;
38
import org.broadinstitute.consent.http.models.ApprovedDataset;
39
import org.broadinstitute.consent.http.models.AuthUser;
40
import org.broadinstitute.consent.http.models.Dataset;
41
import org.broadinstitute.consent.http.models.Error;
42
import org.broadinstitute.consent.http.models.User;
43
import org.broadinstitute.consent.http.models.UserRole;
44
import org.broadinstitute.consent.http.models.UserUpdateFields;
45
import org.broadinstitute.consent.http.service.AcknowledgementService;
46
import org.broadinstitute.consent.http.service.DatasetService;
47
import org.broadinstitute.consent.http.service.UserService;
48
import org.broadinstitute.consent.http.service.UserService.SimplifiedUser;
49
import org.broadinstitute.consent.http.service.sam.SamService;
50

51
@Path("api/user")
52
public class UserResource extends Resource {
53

54
  private final UserService userService;
55
  private final Gson gson = new Gson();
1✔
56
  private final SamService samService;
57
  private final DatasetService datasetService;
58
  private final AcknowledgementService acknowledgementService;
59

60
  @Inject
61
  public UserResource(SamService samService, UserService userService,
62
      DatasetService datasetService, AcknowledgementService acknowledgementService) {
1✔
63
    this.samService = samService;
1✔
64
    this.userService = userService;
1✔
65
    this.datasetService = datasetService;
1✔
66
    this.acknowledgementService = acknowledgementService;
1✔
67
  }
1✔
68

69
  @GET
70
  @Produces("application/json")
71
  @Path("/role/{roleName}")
72
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
73
  public Response getUsers(@Auth AuthUser authUser, @PathParam("roleName") String roleName) {
74
    try {
75
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
76
      boolean valid = UserRoles.isValidRole(roleName);
1✔
77
      if (valid) {
1✔
78
        //if there is a valid roleName but it is not SO or Admin then throw an exception
79
        if (!roleName.equals(UserRoles.ADMIN.getRoleName()) && !roleName.equals(
1✔
80
            UserRoles.SIGNINGOFFICIAL.getRoleName())) {
1✔
81
          throw new BadRequestException("Unsupported role name: " + roleName);
1✔
82
        }
83
        if (!user.hasUserRole(UserRoles.getUserRoleFromName(roleName))) {
1✔
84
          throw new NotFoundException(
1✔
85
              "User: " + user.getDisplayName() + ", does not have " + roleName + " role.");
1✔
86
        }
87
        List<User> users = userService.getUsersAsRole(user, roleName);
1✔
88
        return Response.ok().entity(users).build();
1✔
89
      } else {
90
        throw new BadRequestException("Invalid role name: " + roleName);
1✔
91
      }
92
    } catch (Exception e) {
1✔
93
      return createExceptionResponse(e);
1✔
94
    }
95
  }
96

97
  @GET
98
  @Path("/me")
99
  @Produces("application/json")
100
  @PermitAll
101
  @Timed
102
  public Response getUser(@Auth AuthUser authUser) {
103
    try {
104
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
105
      if (Objects.isNull(authUser.getUserStatusInfo())) {
1✔
106
        samService.asyncPostRegistrationInfo(authUser);
1✔
107
      }
108
      JsonObject userJson = userService.findUserWithPropertiesByIdAsJsonObject(authUser,
1✔
109
          user.getUserId());
1✔
110
      return Response.ok(gson.toJson(userJson)).build();
1✔
111
    } catch (Exception e) {
×
112
      return createExceptionResponse(e);
×
113
    }
114
  }
115

116
  @Deprecated // Use getDatasetsFromUserDacsV2
117
  @GET
118
  @Path("/me/dac/datasets")
119
  @Produces("application/json")
120
  @RolesAllowed({CHAIRPERSON, MEMBER})
121
  public Response getDatasetsFromUserDacs(@Auth AuthUser authUser) {
NEW
122
    return getDatasetsFromUserDacsV2(authUser);
×
123
  }
124

125
  @GET
126
  @Path("/me/dac/datasets/v2")
127
  @Produces("application/json")
128
  @RolesAllowed({CHAIRPERSON, MEMBER})
129
  public Response getDatasetsFromUserDacsV2(@Auth AuthUser authUser) {
130
    try {
131
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
132
      List<Integer> dacIds = user.getRoles().stream()
1✔
133
          .map(UserRole::getDacId)
1✔
134
          .filter(Objects::nonNull)
1✔
135
          .collect(Collectors.toList());
1✔
136
      List<Dataset> datasets =
137
          dacIds.isEmpty() ? List.of() : datasetService.findDatasetListByDacIds(dacIds);
1✔
138
      if (datasets.isEmpty()) {
1✔
139
        throw new NotFoundException("No datasets found for current user");
1✔
140
      }
141
      return Response.ok().entity(datasets).build();
1✔
142
    } catch (Exception e) {
1✔
143
      return createExceptionResponse(e);
1✔
144
    }
145
  }
146

147
  @GET
148
  @Path("/{userId}")
149
  @Produces("application/json")
150
  @RolesAllowed({ADMIN, CHAIRPERSON, MEMBER, DATASUBMITTER, SIGNINGOFFICIAL})
151
  public Response getUserById(@Auth AuthUser authUser, @PathParam("userId") Integer userId) {
152
    try {
153
      JsonObject userJson = userService.findUserWithPropertiesByIdAsJsonObject(authUser, userId);
1✔
154
      return Response.ok(gson.toJson(userJson)).build();
1✔
155
    } catch (Exception e) {
1✔
156
      return createExceptionResponse(e);
1✔
157
    }
158
  }
159

160
  @GET
161
  @Path("/institution/unassigned")
162
  @Produces("application/json")
163
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
164
  public Response getUnassignedUsers(@Auth AuthUser user) {
165
    try {
166
      List<User> unassignedUsers = userService.findUsersWithNoInstitution();
1✔
167
      return Response.ok().entity(unassignedUsers).build();
1✔
168
    } catch (Exception e) {
×
169
      return createExceptionResponse(e);
×
170
    }
171
  }
172

173
  @GET
174
  @Path("/institution/{institutionId}")
175
  @Produces("application/json")
176
  @RolesAllowed({ADMIN})
177
  public Response getUsersByInstitution(
178
      @Auth AuthUser user, @PathParam("institutionId") Integer institutionId) {
179
    try {
180
      List<User> users = userService.findUsersByInstitutionId(institutionId);
1✔
181
      return Response.ok().entity(users).build();
1✔
182
    } catch (Exception e) {
1✔
183
      return createExceptionResponse(e);
1✔
184
    }
185
  }
186

187
  @PUT
188
  @Path("/{id}")
189
  @Consumes("application/json")
190
  @Produces("application/json")
191
  @RolesAllowed({ADMIN})
192
  public Response update(@Auth AuthUser authUser, @Context UriInfo info,
193
      @PathParam("id") Integer userId, String json) {
194
    try {
195
      UserUpdateFields userUpdateFields = gson.fromJson(json, UserUpdateFields.class);
1✔
196
      // Ensure that we have a real user with this ID, fail if we do not.
197
      userService.findUserById(userId);
1✔
198
      User updatedUser = userService.updateUserFieldsById(userUpdateFields, userId);
1✔
199
      Gson gson = new Gson();
1✔
200
      JsonObject jsonUser = userService.findUserWithPropertiesByIdAsJsonObject(authUser,
1✔
201
          updatedUser.getUserId());
1✔
202
      return Response.ok().entity(gson.toJson(jsonUser)).build();
1✔
203
    } catch (Exception e) {
1✔
204
      return createExceptionResponse(e);
1✔
205
    }
206
  }
207

208
  @PUT
209
  @Consumes("application/json")
210
  @Produces("application/json")
211
  @PermitAll
212
  public Response updateSelf(@Auth AuthUser authUser, @Context UriInfo info, String json) {
213
    try {
214
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
215
      UserUpdateFields userUpdateFields = gson.fromJson(json, UserUpdateFields.class);
1✔
216

217
      // Users cannot update their own institution id through this service
218
      if (userUpdateFields.getInstitutionId() != null) {
1✔
219
        throw new BadRequestException("Institution ID is not updatable");
1✔
220
      }
221

222
      if (Objects.nonNull(userUpdateFields.getUserRoleIds()) && !user.hasUserRole(
1✔
223
          UserRoles.ADMIN)) {
224
        throw new BadRequestException("Cannot change user's roles.");
1✔
225
      }
226

227
      user = userService.updateUserFieldsById(userUpdateFields, user.getUserId());
1✔
228
      Gson gson = new Gson();
1✔
229
      JsonObject jsonUser = userService.findUserWithPropertiesByIdAsJsonObject(authUser,
1✔
230
          user.getUserId());
1✔
231

232
      return Response.ok().entity(gson.toJson(jsonUser)).build();
1✔
233
    } catch (Exception e) {
1✔
234
      return createExceptionResponse(e);
1✔
235
    }
236
  }
237

238
  @PUT
239
  @Path("/{userId}/{roleId}")
240
  @Produces("application/json")
241
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
242
  public Response addRoleToUser(@Auth AuthUser authUser, @PathParam("userId") Integer userId,
243
      @PathParam("roleId") Integer roleId) {
244
    UserRoles targetRole = UserRoles.getUserRoleFromId(roleId);
1✔
245
    if (Objects.isNull(targetRole)) {
1✔
246
      return Response.status(HttpStatusCodes.STATUS_CODE_BAD_REQUEST).build();
1✔
247
    }
248
    UserRole role = new UserRole(roleId, targetRole.getRoleName());
1✔
249
    try {
250
      User activeUser = userService.findUserByEmail(authUser.getEmail());
1✔
251
      User user = userService.findUserById(userId);
1✔
252
      List<Integer> currentUserRoleIds = user.getUserRoleIdsFromUser();
1✔
253
      if ((activeUser.hasUserRole(UserRoles.ADMIN) && UserRoles.isValidNonDACRoleId(targetRole)) ||
1✔
254
          signingOfficialMeetsRequirements(targetRole, activeUser, user)) {
1✔
255
        if (!currentUserRoleIds.contains(roleId)) {
1✔
256
          userService.insertRoleAndInstitutionForUser(role, user);
1✔
257
          return getUserResponse(authUser, userId);
1✔
258
        } else {
259
          return Response.notModified().build();
1✔
260
        }
261
      } else {
262
        return Response.status(HttpStatusCodes.STATUS_CODE_BAD_REQUEST).build();
1✔
263
      }
264
    } catch (Exception e) {
1✔
265
      return createExceptionResponse(e);
1✔
266
    }
267
  }
268

269
  private static boolean signingOfficialMeetsRequirements(UserRoles role, User activeUser,
270
      User user) {
271
    return activeUser.hasUserRole(UserRoles.SIGNINGOFFICIAL)
1✔
272
        && activeUser.getInstitutionId() != null
1✔
273
        && UserRoles.isValidSoAdjustableRoleId(role)
1✔
274
        && (user.getInstitutionId() == null || user.getInstitutionId().equals(activeUser.getInstitutionId()));
1✔
275
  }
276

277
  private Response getUserResponse(AuthUser authUser, Integer userId) {
278
    JsonObject userJson = userService.findUserWithPropertiesByIdAsJsonObject(authUser, userId);
1✔
279
    return Response.ok().entity(gson.toJson(userJson)).build();
1✔
280
  }
281

282
  @DELETE
283
  @Path("/{userId}/{roleId}")
284
  @Produces("application/json")
285
  @RolesAllowed({ADMIN, SIGNINGOFFICIAL})
286
  public Response deleteRoleFromUser(@Auth AuthUser authUser, @PathParam("userId") Integer userId,
287
      @PathParam("roleId") Integer roleId) {
288
    UserRoles targetRole = UserRoles.getUserRoleFromId(roleId);
1✔
289
    if (Objects.isNull(targetRole)) {
1✔
290
      return Response.status(HttpStatusCodes.STATUS_CODE_BAD_REQUEST).build();
1✔
291
    }
292
    try {
293
      User activeUser = userService.findUserByEmail(authUser.getEmail());
1✔
294
      User user = userService.findUserById(userId);
1✔
295
      if (activeUser.hasUserRole(UserRoles.ADMIN)) {
1✔
296
        if (!UserRoles.isValidNonDACRoleId(targetRole)) {
1✔
297
          throw new BadRequestException("Invalid Role Id");
×
298
        }
299
        return doDelete(authUser, userId, roleId, activeUser, user);
1✔
300
      } else if (activeUser.hasUserRole(UserRoles.SIGNINGOFFICIAL)) {
1✔
301
        if (!UserRoles.isValidSoAdjustableRoleId(targetRole)) {
1✔
302
          throw new ForbiddenException(
1✔
303
              "A Signing Official may only remove the following role ids: [6, 7, 8] ");
304
        }
305
        if (Objects.equals(user.getUserId(), activeUser.getUserId())
1✔
306
            && (UserRoles.getUserRoleFromId(roleId) == UserRoles.SIGNINGOFFICIAL)) {
1✔
307
          throw new BadRequestException(
1✔
308
              "You cannot remove the SIGNINGOFFICIAL role from yourself.");
309
        }
310
        if (Objects.nonNull(activeUser.getInstitutionId())
1✔
311
            && Objects.equals(activeUser.getInstitutionId(), user.getInstitutionId())) {
1✔
312
          return doDelete(authUser, userId, roleId, activeUser, user);
1✔
313
        } else {
314
          throw new ForbiddenException("Not authorized to remove roles");
1✔
315
        }
316
      } else {
317
        throw new ForbiddenException("Not authorized to remove roles.");
×
318
      }
319
    } catch (Exception e) {
1✔
320
      return createExceptionResponse(e);
1✔
321
    }
322
  }
323

324
  private Response doDelete(AuthUser authUser, Integer userId, Integer roleId, User activeUser,
325
      User user) {
326
    List<Integer> currentUserRoleIds = user.getUserRoleIdsFromUser();
1✔
327
    if (!currentUserRoleIds.contains(roleId)) {
1✔
328
      JsonObject userJson = userService.findUserWithPropertiesByIdAsJsonObject(authUser, userId);
1✔
329
      return Response.ok().entity(gson.toJson(userJson)).build();
1✔
330
    }
331
    userService.deleteUserRole(activeUser, userId, roleId);
1✔
332
    JsonObject userJson = userService.findUserWithPropertiesByIdAsJsonObject(authUser, userId);
1✔
333
    return Response.ok().entity(gson.toJson(userJson)).build();
1✔
334
  }
335

336
  @POST
337
  @Consumes("application/json")
338
  @Produces("application/json")
339
  @PermitAll
340
  public Response createResearcher(@Context UriInfo info, @Auth AuthUser authUser) {
341
    if (authUser == null || authUser.getEmail() == null || authUser.getName() == null) {
1✔
342
      return Response.
1✔
343
          status(Response.Status.BAD_REQUEST).
1✔
344
          entity(new Error("Unable to verify google identity",
1✔
345
              Response.Status.BAD_REQUEST.getStatusCode())).
1✔
346
          build();
1✔
347
    }    try {
348
      if (userService.findUserByEmail(authUser.getEmail()) != null) {
1✔
349
        return Response.
1✔
350
            status(Response.Status.CONFLICT).
1✔
351
            entity(new Error("Registered user exists", Response.Status.CONFLICT.getStatusCode())).
1✔
352
            build();
1✔
353
      }
354
    } catch (NotFoundException nfe) {
1✔
355
      // no-op, we expect to not find the new user in this case.
356
    }
×
357
    User user = new User();
1✔
358
    user.setEmail(authUser.getEmail());
1✔
359
    user.setDisplayName(authUser.getName());
1✔
360
    user.setResearcherRole();
1✔
361
    try {
362
      URI uri;
363
      user = userService.createUser(user);
1✔
364
      uri = info.getRequestUriBuilder().path("{email}").build(user.getEmail());
1✔
365
      return Response.created(new URI(uri.toString().replace("user", "dacuser"))).entity(user)
1✔
366
          .build();
1✔
367
    } catch (Exception e) {
×
368
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
×
369
          .entity(new Error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()))
×
370
          .build();
×
371
    }
372
  }
373

374
  @DELETE
375
  @Produces(MediaType.APPLICATION_JSON)
376
  @Path("/{email}")
377
  @RolesAllowed(ADMIN)
378
  public Response delete(@Auth AuthUser authUser, @PathParam("email") String email, @Context UriInfo info) {
379
    userService.deleteUserByEmail(email);
1✔
380
    return Response.ok().build();
1✔
381
  }
382

383
  @GET
384
  @Produces(MediaType.APPLICATION_JSON)
385
  @Path("/signing-officials")
386
  @RolesAllowed(RESEARCHER)
387
  public Response getSOsForInstitution(@Auth AuthUser authUser) {
388
    try {
389
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
390
      if (Objects.nonNull(user.getInstitutionId())) {
1✔
391
        List<SimplifiedUser> signingOfficials = userService.findSOsByInstitutionId(
1✔
392
            user.getInstitutionId());
1✔
393
        return Response.ok().entity(signingOfficials).build();
1✔
394
      }
395
      return Response.ok().entity(Collections.emptyList()).build();
1✔
396
    } catch (Exception e) {
1✔
397
      return createExceptionResponse(e);
1✔
398
    }
399
  }
400

401
  @GET
402
  @Produces(MediaType.APPLICATION_JSON)
403
  @Path("/acknowledgements")
404
  @PermitAll
405
  public Response getUserAcknowledgements(@Auth AuthUser authUser) {
406
    try {
407
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
408
      Map<String, Acknowledgement> acknowledgementMap = acknowledgementService.findAcknowledgementsForUser(
1✔
409
          user);
410
      return Response.ok().entity(acknowledgementMap).build();
1✔
411
    } catch (Exception e) {
1✔
412
      return createExceptionResponse(e);
1✔
413
    }
414
  }
415

416
  @GET
417
  @Produces(MediaType.APPLICATION_JSON)
418
  @Path("/acknowledgements/{key}")
419
  @PermitAll
420
  public Response getUserAcknowledgement(@Auth AuthUser authUser, @PathParam("key") String key) {
421
    try {
422
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
423
      Acknowledgement ack = acknowledgementService.findAcknowledgementForUserByKey(user, key);
1✔
424
      if (ack == null) {
1✔
425
        return Response.status(Response.Status.NOT_FOUND).build();
1✔
426
      }
427
      return Response.ok().entity(ack).build();
1✔
428
    } catch (Exception e) {
1✔
429
      return createExceptionResponse(e);
1✔
430
    }
431
  }
432

433
  @DELETE
434
  @Produces(MediaType.APPLICATION_JSON)
435
  @Path("/acknowledgements/{key}")
436
  @RolesAllowed(ADMIN)
437
  public Response deleteUserAcknowledgement(@Auth AuthUser authUser, @PathParam("key") String key) {
438
    try {
439
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
440
      Acknowledgement ack = acknowledgementService.findAcknowledgementForUserByKey(user, key);
1✔
441
      if (Objects.isNull(ack)) {
1✔
442
        return Response.status(Response.Status.NOT_FOUND).build();
1✔
443
      }
444
      acknowledgementService.deleteAcknowledgementForUserByKey(user, key);
1✔
445
      return Response.ok().entity(ack).build();
1✔
446
    } catch (Exception e) {
×
447
      return createExceptionResponse(e);
×
448
    }
449
  }
450

451
  @POST
452
  @Consumes(MediaType.APPLICATION_JSON)
453
  @Produces(MediaType.APPLICATION_JSON)
454
  @Path("/acknowledgements")
455
  @PermitAll
456
  public Response postAcknowledgements(@Auth AuthUser authUser, String json) {
457
    ArrayList<String> keys;
458
    try {
459
      Type listOfStringsType = new TypeToken<ArrayList<String>>() {
1✔
460
      }.getType();
1✔
461
      keys = gson.fromJson(json, listOfStringsType);
1✔
462
      if (keys == null || keys.isEmpty()) {
1✔
463
        return Response.status(Response.Status.BAD_REQUEST).build();
1✔
464
      }
465
    } catch (Exception e) {
1✔
466
      return Response.status(Response.Status.BAD_REQUEST).build();
1✔
467
    }
1✔
468

469
    try {
470
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
471
      Map<String, Acknowledgement> acknowledgementMap = acknowledgementService.makeAcknowledgements(
1✔
472
          keys, user);
473
      return Response.ok().entity(acknowledgementMap).build();
1✔
474
    } catch (Exception e) {
1✔
475
      return createExceptionResponse(e);
1✔
476
    }
477
  }
478

479
  @GET
480
  @Produces(MediaType.APPLICATION_JSON)
481
  @Path("/me/researcher/datasets")
482
  @PermitAll
483
  public Response getApprovedDatasets(@Auth AuthUser authUser) {
484
    try {
485
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
486
      List<ApprovedDataset> approvedDatasets = datasetService.getApprovedDatasets(user);
1✔
487
      return Response.ok().entity(approvedDatasets).build();
1✔
488
    } catch (Exception e) {
×
489
      return createExceptionResponse(e);
×
490
    }
491
  }
492

493

494
}
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