From 26c2fb4106636d1ea41b690cc67448adfcc95fe7 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 11 May 2023 08:03:27 -0400 Subject: [PATCH 01/35] Add beginnings of register participant admin command --- .../org/broadinstitute/dsm/DSMServer.java | 4 ++ .../dsm/route/RegisterParticipantRoute.java | 52 +++++++++++++++++++ .../broadinstitute/dsm/statics/RoutePath.java | 1 + 3 files changed, 57 insertions(+) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 57314d0eb9..3a8ddb05e6 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -95,6 +95,7 @@ import org.broadinstitute.dsm.route.KitDeactivationRoute; import org.broadinstitute.dsm.route.KitDiscardRoute; import org.broadinstitute.dsm.route.KitExpressRoute; +import org.broadinstitute.dsm.route.RegisterParticipantRoute; import org.broadinstitute.dsm.route.kit.KitFinalScanRoute; import org.broadinstitute.dsm.route.KitLabelRoute; import org.broadinstitute.dsm.route.KitRequestRoute; @@ -893,6 +894,9 @@ private void setupMiscellaneousRoutes() { FrontendAnalyticsRoute frontendAnalyticsRoute = new FrontendAnalyticsRoute(); patch(uiRoot + RoutePath.GoogleAnalytics, frontendAnalyticsRoute, new JsonTransformer()); + + RegisterParticipantRoute registerParticipantRoute = new RegisterParticipantRoute(); + post(uiRoot + RoutePath.REGISTER_PARTICIPANT, registerParticipantRoute, new JsonTransformer()); } private void setupSharedRoutes(@NonNull KitUtil kitUtil, @NonNull NotificationUtil notificationUtil, @NonNull PatchUtil patchUtil) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java new file mode 100644 index 0000000000..8cb88bbc1e --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java @@ -0,0 +1,52 @@ +package org.broadinstitute.dsm.route; + +import org.apache.commons.lang3.StringUtils; +import org.broadinstitute.dsm.model.Study; +import org.broadinstitute.dsm.model.defaultvalues.Defaultable; +import org.broadinstitute.dsm.model.defaultvalues.DefaultableMaker; +import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.dsm.util.ParticipantUtil; +import org.broadinstitute.lddp.handlers.util.Result; +import spark.QueryParamsMap; +import spark.Request; +import spark.Response; + +public class RegisterParticipantRoute extends RequestHandler { + @Override + protected Object processRequest(Request request, Response response, String userId) throws Exception { + + QueryParamsMap queryParamsMap = request.queryMap(); + + String participantId = queryParamsMap.get(RoutePath.DDP_PARTICIPANT_ID).value(); + if (StringUtils.isBlank(participantId)) { + throw new IllegalArgumentException("participant ID cannot be empty"); + } + + String realm = queryParamsMap.get(RoutePath.REALM).value(); + if (StringUtils.isBlank(realm)) { + throw new IllegalArgumentException("realm cannot be empty"); + } + + // TODO: check user access + + if (!ParticipantUtil.isGuid(participantId)) { + throw new IllegalArgumentException("invalid participant ID"); + } + + try { + Study study = Study.of(realm.toUpperCase()); + Defaultable defaultable = DefaultableMaker.makeDefaultable(study); + boolean result = defaultable.generateDefaults(realm, participantId); + if (!result) { + // TODO: fix message and exception type + throw new IllegalArgumentException("invalid participant ID"); + } + return new Result(200); + } catch (Exception e) { + // TODO: fix status code + response.status(500); + return new Result(500, e.getMessage()); + } + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index dd83c3aa30..34729eef21 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -93,6 +93,7 @@ public class RoutePath { public static final String GET_DEFAULT_FILTERS = "getFiltersDefault"; public static final String GET_PARTICIPANT = "getParticipant"; public static final String GET_PARTICIPANT_DATA = "getParticipantData"; + public static final String REGISTER_PARTICIPANT = "registerParticipant"; public static final String TISSUE_LIST = "tissueList"; public static final String NDI_REQUEST = "ndiRequest"; public static final String ABSTRACTION_FORM_CONTROLS = "abstractionformcontrols"; From c228632cf18fe8abb8b5ea553e6c7a13071000ac Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 16 Jun 2023 13:37:26 -0400 Subject: [PATCH 02/35] WIP --- .../org/broadinstitute/dsm/DSMServer.java | 2 +- .../dsm/route/admin/AddUserRoleRequest.java | 15 ++++ .../dsm/route/admin/AddUserRoleRoute.java | 72 +++++++++++++++++++ .../{ => admin}/RegisterParticipantRoute.java | 2 +- .../dsm/service/admin/UserAdminService.java | 10 +++ 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java rename pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/{ => admin}/RegisterParticipantRoute.java (97%) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index bed22d522b..ad15bf99b3 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -114,7 +114,7 @@ import org.broadinstitute.dsm.route.ParticipantExitRoute; import org.broadinstitute.dsm.route.ParticipantStatusRoute; import org.broadinstitute.dsm.route.PatchRoute; -import org.broadinstitute.dsm.route.RegisterParticipantRoute; +import org.broadinstitute.dsm.route.admin.RegisterParticipantRoute; import org.broadinstitute.dsm.route.TriggerSurveyRoute; import org.broadinstitute.dsm.route.UserSettingRoute; import org.broadinstitute.dsm.route.ViewFilterRoute; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java new file mode 100644 index 0000000000..f50c1cdcbb --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java @@ -0,0 +1,15 @@ +package org.broadinstitute.dsm.route.admin; + +public class AddUserRoleRequest { + + private final String email; + private final String group; + private final String role; + + public AddUserRoleRequest(String email, String group, String role) { + this.email = email; + this.group = group; + this.role = role; + } + +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java new file mode 100644 index 0000000000..6408fee32e --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java @@ -0,0 +1,72 @@ +package org.broadinstitute.dsm.route.admin; + +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.broadinstitute.dsm.pubsub.EditParticipantMessagePublisher; +import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.dsm.statics.UserErrorMessages; +import org.broadinstitute.dsm.util.UserUtil; +import org.broadinstitute.lddp.handlers.util.Result; +import spark.QueryParamsMap; +import spark.Request; +import spark.Response; + +@Slf4j +public class AddUserRoleRoute extends RequestHandler { + + @Override + public Object processRequest(Request request, Response response, String userId) throws Exception { + + QueryParamsMap queryParams = request.queryMap(); + + String realm = null; + if (queryParams.value(RoutePath.REALM) != null) { + realm = queryParams.get(RoutePath.REALM).value(); + } + String userIdRequest = UserUtil.getUserId(request); + if (UserUtil.checkUserAccess(realm, userId, "participant_edit", userIdRequest)) { + String messageData = request.body(); + + if (StringUtils.isBlank(messageData)) { + logger.error("Message data is blank"); + } + + JsonObject messageJsonObject = new Gson().fromJson(messageData, JsonObject.class); + + JsonObject dataFromJson = messageJsonObject.get("data").getAsJsonObject(); + + String data = dataFromJson.toString(); + + Map attributeMap = getStringStringMap(userId, messageJsonObject); + + try { + EditParticipantMessagePublisher.publishMessage(data, attributeMap, projectId, topicId); + } catch (Exception e) { + e.printStackTrace(); + } + + return new Result(200); + } else { + response.status(500); + return new Result(500, UserErrorMessages.NO_RIGHTS); + } + + } + /* + SELECT @uemail := ""; + +SELECT @uid := user_id FROM access_user WHERE email COLLATE utf8mb4_general_ci = @uemail; + + +SELECT @gid := group_id from ddp_group where name COLLATE utf8mb4_general_ci = "pecgs" ; + +INSERT INTO `dev_dsm_db`.`access_user_role_group` (`user_id`, `role_id`, `group_id`) + SELECT @uid, role_id, @gid FROM access_role WHERE name in ('onc_history_upload', 'upload_ror_file'); + */ + +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java similarity index 97% rename from pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java rename to pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java index 8cb88bbc1e..086039a71a 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/RegisterParticipantRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java @@ -1,4 +1,4 @@ -package org.broadinstitute.dsm.route; +package org.broadinstitute.dsm.route.admin; import org.apache.commons.lang3.StringUtils; import org.broadinstitute.dsm.model.Study; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java new file mode 100644 index 0000000000..ee4c42d7b3 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -0,0 +1,10 @@ +package org.broadinstitute.dsm.service.admin; + +import org.broadinstitute.dsm.route.admin.AddUserRoleRequest; + +public class UserAdminService { + + public static void addUserToRole(AddUserRoleRequest req) { + + } +} From c68c7797930a4a2468e5247883532be1b13f75fb Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 19 Jun 2023 11:06:59 -0400 Subject: [PATCH 03/35] WIP --- .../dsm/route/admin/AddUserRoleRequest.java | 3 ++ .../dsm/service/admin/UserAdminService.java | 46 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java index f50c1cdcbb..f1f6c66f06 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java @@ -1,5 +1,8 @@ package org.broadinstitute.dsm.route.admin; +import lombok.Data; + +@Data public class AddUserRoleRequest { private final String email; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index ee4c42d7b3..4e86a0245d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -1,10 +1,54 @@ package org.broadinstitute.dsm.service.admin; +import java.util.Optional; + +import org.broadinstitute.dsm.db.dao.user.UserDao; +import org.broadinstitute.dsm.db.dto.user.UserDto; +import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.route.admin.AddUserRoleRequest; public class UserAdminService { - public static void addUserToRole(AddUserRoleRequest req) { + private final String studyGroup; + private final String operatorId; + + private static final String SQL_SELECT_ASSIGNEE = + "SELECT user.user_id, user.name, user.email FROM access_user_role_group roleGroup, access_user user, access_role role, " + + "ddp_group, ddp_instance_group realmGroup, ddp_instance realm WHERE roleGroup.user_id = user.user_id AND roleGroup" + + ".role_id = role.role_id AND realm.ddp_instance_id = realmGroup.ddp_instance_id" + + " AND realmGroup.ddp_group_id = ddp_group.group_id AND ddp_group.group_id = roleGroup.group_id " + + "AND role.name = \"mr_request\" AND realm.instance_name = ?"; + + private static final String SQL_SELECT_ROLES_FOR_INSTANCE = + "SELECT role.name FROM ddp_instance ddp left join ddp_instance_role inRol on (inRol.ddp_instance_id = ddp.ddp_instance_id) " + + " left join instance_role role on (role.instance_role_id = inRol.instance_role_id) " + + "WHERE is_active = 1 and instance_name = ? "; + + private static final String SQL_SELECT_ROLE_FOR_STUDY_GROUP = + "select ar.name from access_role ar" + + "join access_user_role_group aurg on aurg.role_id = ar.role_id" + + "join ddp_group dg on aurg.group_id = dg.group_id" + + "where dg.name = ? and ar.name = ?"; + + public UserAdminService(String studyGroup, String operatorId) { + this.studyGroup = studyGroup; + this.operatorId = operatorId; + } + + public void addUserToRole(AddUserRoleRequest req) { + + UserDao userDao = new UserDao(); + Optional res = userDao.getUserByEmail(req.getEmail()); + if (res.isEmpty()) { + throw new DSMBadRequestException("Invalid user: " + req.getEmail()); + } + + UserDto userDto = res.get(); + // TODO: determine if operator can admin this user + + + // assure role is for this study group + userDto. } } From 57e80dcbb38fc9b040cc75f68deeda4af6b18b66 Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 19 Jun 2023 21:36:52 -0400 Subject: [PATCH 04/35] Fix checkstyle violation --- .../src/main/java/org/broadinstitute/dsm/DSMServer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 0a14a0c8a2..eedd27e7f9 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -590,7 +590,8 @@ protected void setupCustomRouting(@NonNull Config cfg) { if (!cfg.getBoolean("ui.production")) { get(apiRoot + RoutePath.CREATE_CLINICAL_KIT_ENDPOINT, new CreateClinicalDummyKitRoute(new OncHistoryDetailDaoImpl()), new JsonTransformer()); - get(apiRoot + RoutePath.CREATE_CLINICAL_KIT_ENDPOINT_WITH_PARTICIPANT, new CreateClinicalDummyKitRoute(new OncHistoryDetailDaoImpl()), + get(apiRoot + RoutePath.CREATE_CLINICAL_KIT_ENDPOINT_WITH_PARTICIPANT, + new CreateClinicalDummyKitRoute(new OncHistoryDetailDaoImpl()), new JsonTransformer()); get(apiRoot + RoutePath.DUMMY_ENDPOINT, new CreateBSPDummyKitRoute(), new JsonTransformer()); } From d1e1e998079a0883240be97c2306cfb6c692bb9e Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 20 Jun 2023 10:29:57 -0400 Subject: [PATCH 05/35] WIP --- .../dsm/route/admin/AddUserRoleRequest.java | 18 -------- .../dsm/service/admin/AddUserRoleRequest.java | 18 ++++++++ .../dsm/service/admin/UserAdminService.java | 45 ++++++++++++++++--- 3 files changed, 56 insertions(+), 25 deletions(-) delete mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java deleted file mode 100644 index f1f6c66f06..0000000000 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.broadinstitute.dsm.route.admin; - -import lombok.Data; - -@Data -public class AddUserRoleRequest { - - private final String email; - private final String group; - private final String role; - - public AddUserRoleRequest(String email, String group, String role) { - this.email = email; - this.group = group; - this.role = role; - } - -} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java new file mode 100644 index 0000000000..576d913fdf --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java @@ -0,0 +1,18 @@ +package org.broadinstitute.dsm.service.admin; + +import lombok.Data; + +@Data +public class AddUserRoleRequest { + + private final String email; + private final String studyGroup; + private final String role; + + public AddUserRoleRequest(String email, String studyGroup, String role) { + this.email = email; + this.studyGroup = studyGroup; + this.role = role; + } + +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 4e86a0245d..6efcd09e84 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -5,7 +5,8 @@ import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.db.dto.user.UserDto; import org.broadinstitute.dsm.exception.DSMBadRequestException; -import org.broadinstitute.dsm.route.admin.AddUserRoleRequest; +import org.broadinstitute.dsm.util.UserUtil; +import spark.utils.StringUtils; public class UserAdminService { @@ -35,20 +36,50 @@ public UserAdminService(String studyGroup, String operatorId) { this.operatorId = operatorId; } - public void addUserToRole(AddUserRoleRequest req) { + public void addUserToRole(AddUserRoleRequest req, String operatorId) { + + // TODO: determine if operator can admin this study group user + + String email = req.getEmail(); + if (StringUtils.isBlank(email)) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + String group = req.getStudyGroup(); + if (StringUtils.isBlank(group)) { + throw new DSMBadRequestException("Invalid study group: blank"); + } + String role = req.getRole(); + if (StringUtils.isBlank(role)) { + throw new DSMBadRequestException("Invalid role: blank"); + } UserDao userDao = new UserDao(); - Optional res = userDao.getUserByEmail(req.getEmail()); + Optional res = userDao.getUserByEmail(email); if (res.isEmpty()) { - throw new DSMBadRequestException("Invalid user: " + req.getEmail()); + throw new DSMBadRequestException("Invalid user: " + email); } - UserDto userDto = res.get(); - // TODO: determine if operator can admin this user + int studyGroupId = getStudyGroup(studyGroup, role); + UserDto userDto = res.get(); + // assure role is for this study group - userDto. + int userId = userDto.getId(); + // insert + } + + private boolean operatorIsAdmin(String operatorId, int studyGroupId) { + return true; + } + + /** + * Validate study group and assure role is valid for study group + * @return study group ID + * @throws DSMBadRequestException if study group is invalid or role is not valid for group + */ + private int getStudyGroup(String studyGroup, int roleId) { + return 1; } } From d9fdd4a81765fb2a2426472802aab14538ab2bed Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 20 Jun 2023 15:12:53 -0400 Subject: [PATCH 06/35] WIP --- .../broadinstitute/dsm/route/admin/AddUserRoleRoute.java | 7 ++++--- .../broadinstitute/dsm/service/admin/UserAdminService.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java index 6408fee32e..4f0ec9abeb 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java @@ -22,7 +22,8 @@ public class AddUserRoleRoute extends RequestHandler { @Override public Object processRequest(Request request, Response response, String userId) throws Exception { - QueryParamsMap queryParams = request.queryMap(); + return 1; +/* QueryParamsMap queryParams = request.queryMap(); String realm = null; if (queryParams.value(RoutePath.REALM) != null) { @@ -33,7 +34,7 @@ public Object processRequest(Request request, Response response, String userId) String messageData = request.body(); if (StringUtils.isBlank(messageData)) { - logger.error("Message data is blank"); + log.error("Message data is blank"); } JsonObject messageJsonObject = new Gson().fromJson(messageData, JsonObject.class); @@ -54,7 +55,7 @@ public Object processRequest(Request request, Response response, String userId) } else { response.status(500); return new Result(500, UserErrorMessages.NO_RIGHTS); - } + }*/ } /* diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 6efcd09e84..f9e71bfb1d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -59,7 +59,7 @@ public void addUserToRole(AddUserRoleRequest req, String operatorId) { throw new DSMBadRequestException("Invalid user: " + email); } - int studyGroupId = getStudyGroup(studyGroup, role); + //int studyGroupId = getStudyGroup(studyGroup, role); UserDto userDto = res.get(); From e436d113cdd47424583e9fa2e2a0c7ca12d7ab9b Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 21 Jun 2023 16:33:45 -0400 Subject: [PATCH 07/35] WIP --- .../dsm/route/admin/AddUserRoleRoute.java | 71 ++-- .../service/admin/AddStudyRoleRequest.java | 14 + .../dsm/service/admin/UserAdminService.java | 394 ++++++++++++++++-- .../service/admin/UserAdminServiceTest.java | 118 ++++++ 4 files changed, 525 insertions(+), 72 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java create mode 100644 pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java index 4f0ec9abeb..d5affb528c 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java @@ -1,18 +1,14 @@ package org.broadinstitute.dsm.route.admin; -import java.util.Map; - import com.google.gson.Gson; -import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.broadinstitute.dsm.pubsub.EditParticipantMessagePublisher; +import org.broadinstitute.dsm.exception.DSMBadRequestException; +import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.security.RequestHandler; -import org.broadinstitute.dsm.statics.RoutePath; -import org.broadinstitute.dsm.statics.UserErrorMessages; -import org.broadinstitute.dsm.util.UserUtil; +import org.broadinstitute.dsm.service.admin.AddUserRoleRequest; +import org.broadinstitute.dsm.service.admin.UserAdminService; import org.broadinstitute.lddp.handlers.util.Result; -import spark.QueryParamsMap; import spark.Request; import spark.Response; @@ -20,43 +16,42 @@ public class AddUserRoleRoute extends RequestHandler { @Override - public Object processRequest(Request request, Response response, String userId) throws Exception { - - return 1; -/* QueryParamsMap queryParams = request.queryMap(); + public Object processRequest(Request request, Response response, String userId) { - String realm = null; - if (queryParams.value(RoutePath.REALM) != null) { - realm = queryParams.get(RoutePath.REALM).value(); + String body = request.body(); + if (StringUtils.isBlank(body)) { + response.status(400); + return "Request body is blank"; } - String userIdRequest = UserUtil.getUserId(request); - if (UserUtil.checkUserAccess(realm, userId, "participant_edit", userIdRequest)) { - String messageData = request.body(); - - if (StringUtils.isBlank(messageData)) { - log.error("Message data is blank"); - } - - JsonObject messageJsonObject = new Gson().fromJson(messageData, JsonObject.class); - - JsonObject dataFromJson = messageJsonObject.get("data").getAsJsonObject(); - String data = dataFromJson.toString(); - - Map attributeMap = getStringStringMap(userId, messageJsonObject); + AddUserRoleRequest req; + try { + req = new Gson().fromJson(body, AddUserRoleRequest.class); + log.info("TEMP: AddUserRoleRequest {}", req); + } catch (Exception e) { + log.info("Invalid request format for {}", body); + response.status(400); + return "Invalid request format"; + } - try { - EditParticipantMessagePublisher.publishMessage(data, attributeMap, projectId, topicId); - } catch (Exception e) { - e.printStackTrace(); - } + UserAdminService adminService = new UserAdminService(userId); - return new Result(200); - } else { + try { + adminService.addUserToRole(req); + } catch (DSMBadRequestException e) { + response.status(400); + return e.getMessage(); + } catch (DsmInternalError e) { + log.error("Error adding user to role: {}", e.getMessage()); response.status(500); - return new Result(500, UserErrorMessages.NO_RIGHTS); - }*/ + return "Internal error. Contact development team"; + } catch (Exception e) { + log.error("Error adding user to role: {}", e.getMessage()); + response.status(500); + return e.getMessage(); + } + return new Result(200); } /* SELECT @uemail := ""; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java new file mode 100644 index 0000000000..db7f172397 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java @@ -0,0 +1,14 @@ +package org.broadinstitute.dsm.service.admin; + +import lombok.Data; + +@Data +public class AddStudyRoleRequest { + private final String studyGroup; + private final String role; + + public AddStudyRoleRequest(String studyGroup, String role) { + this.studyGroup = studyGroup; + this.role = role; + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index f9e71bfb1d..003589719e 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -1,18 +1,28 @@ package org.broadinstitute.dsm.service.admin; -import java.util.Optional; +import static org.broadinstitute.ddp.db.TransactionWrapper.inTransaction; -import org.broadinstitute.dsm.db.dao.user.UserDao; -import org.broadinstitute.dsm.db.dto.user.UserDto; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; import org.broadinstitute.dsm.exception.DSMBadRequestException; -import org.broadinstitute.dsm.util.UserUtil; +import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.lddp.db.SimpleResult; import spark.utils.StringUtils; +@Slf4j public class UserAdminService { - private final String studyGroup; private final String operatorId; + private static final String STUDY_ADMIN_ROLE = "study_admin"; + private static final String SQL_SELECT_ASSIGNEE = "SELECT user.user_id, user.name, user.email FROM access_user_role_group roleGroup, access_user user, access_role role, " + "ddp_group, ddp_instance_group realmGroup, ddp_instance realm WHERE roleGroup.user_id = user.user_id AND roleGroup" @@ -20,30 +30,83 @@ public class UserAdminService { + " AND realmGroup.ddp_group_id = ddp_group.group_id AND ddp_group.group_id = roleGroup.group_id " + "AND role.name = \"mr_request\" AND realm.instance_name = ?"; - private static final String SQL_SELECT_ROLES_FOR_INSTANCE = - "SELECT role.name FROM ddp_instance ddp left join ddp_instance_role inRol on (inRol.ddp_instance_id = ddp.ddp_instance_id) " - + " left join instance_role role on (role.instance_role_id = inRol.instance_role_id) " - + "WHERE is_active = 1 and instance_name = ? "; + private static final String SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID = + "SELECT ar.role_id, ar.name, dg.group_id FROM access_role ar " + + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + + "JOIN ddp_group dg on dg.group_id = aurg.group_id " + + "JOIN access_user au on au.user_id = aurg.user_id " + + "WHERE au.user_id = ? AND dg.name = ?"; + + private static final String SQL_SELECT_USER_ROLE = + "SELECT aurg.user_role_group_id FROM access_user_role_group aurg " + + "JOIN access_role ar on ar.role_id = aurg.role_id " + + "JOIN ddp_group dg on dg.group_id = aurg.group_id " + + "JOIN access_user au on au.user_id = aurg.user_id " + + "WHERE au.user_id = ? AND dg.name = ? AND ar.name = ?"; + + private static final String SQL_SELECT_ROLE = + "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; + + private static final String SQL_SELECT_GROUP = + "SELECT dg.group_id FROM ddp_group dg WHERE dg.name = ?"; + + private static final String SQL_SELECT_USER_BY_EMAIL_AND_GROUP_ID = + "select distinct(au.user_id), au.name FROM access_user au " + + "JOIN access_user_role_group aurg on aurg.user_id = au.user_id " + + "JOIN ddp_group dg on aurg.group_id = ? " + + "WHERE au.email = ?"; + + private static final String SQL_INSERT_USER_ROLE = + "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; + + private static final String SQL_DELETE_USER_ROLE = + "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ?"; + + private static final String SQL_INSERT_ROLE = + "INSERT INTO access_role SET name = ?"; + + private static final String SQL_INSERT_GROUP = + "INSERT INTO ddp_group SET name = ?"; - private static final String SQL_SELECT_ROLE_FOR_STUDY_GROUP = - "select ar.name from access_role ar" - + "join access_user_role_group aurg on aurg.role_id = ar.role_id" - + "join ddp_group dg on aurg.group_id = dg.group_id" - + "where dg.name = ? and ar.name = ?"; + private static final String SQL_DELETE_GROUP = + "DELETE FROM ddp_group WHERE group_id = ?"; - public UserAdminService(String studyGroup, String operatorId) { - this.studyGroup = studyGroup; + public UserAdminService(String operatorId) { this.operatorId = operatorId; } - public void addUserToRole(AddUserRoleRequest req, String operatorId) { + public void addUserToRole(AddUserRoleRequest req) { // TODO: determine if operator can admin this study group user + String group = req.getStudyGroup(); + if (StringUtils.isBlank(group)) { + throw new DSMBadRequestException("Invalid study group: blank"); + } String email = req.getEmail(); if (StringUtils.isBlank(email)) { throw new DSMBadRequestException("Invalid user email: blank"); } + String role = req.getRole(); + if (StringUtils.isBlank(role)) { + throw new DSMBadRequestException("Invalid role: blank"); + } + + int groupId = verifyOperatorForGroup(operatorId, group); + + int roleId = verifyRole(role, groupId); + + int userId = getUserByEmailAndGroup(email, groupId); + + try { + addUserRole(userId, roleId, groupId); + } catch (Exception e) { + String msg = String.format("Error adding user %s to role %s", email, role); + throw new DsmInternalError(msg, e); + } + } + + public void addStudyRole(AddStudyRoleRequest req) { String group = req.getStudyGroup(); if (StringUtils.isBlank(group)) { throw new DSMBadRequestException("Invalid study group: blank"); @@ -53,33 +116,296 @@ public void addUserToRole(AddUserRoleRequest req, String operatorId) { throw new DSMBadRequestException("Invalid role: blank"); } - UserDao userDao = new UserDao(); - Optional res = userDao.getUserByEmail(email); - if (res.isEmpty()) { - throw new DSMBadRequestException("Invalid user: " + email); + int groupId = verifyOperatorForGroup(operatorId, group); + + try { + addRole(role, groupId); + } catch (Exception e) { + String msg = String.format("Error adding role %s to study group %s", role, group); + throw new DsmInternalError(msg, e); + } + } + + protected static int verifyOperatorForGroup(String operatorId, String studyGroup) { + + List roles = getRolesForUser(operatorId, studyGroup); + if (roles.isEmpty()) { + throw new DSMBadRequestException("No roles found for operator: " + operatorId); + } + List roleNames = roles.stream().map(r -> r.roleName).collect(Collectors.toList()); + if (!roleNames.contains(STUDY_ADMIN_ROLE)) { + throw new DSMBadRequestException("Operator does not have administrator privileges: " + operatorId); } + return roles.get(0).groupId; + } - //int studyGroupId = getStudyGroup(studyGroup, role); + protected static List getRolesForUser(String userId, String studyGroup) { + SimpleResult res = inTransaction(conn -> { + List roles = new ArrayList<>(); + SimpleResult dbVals = new SimpleResult(roles); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID)) { + stmt.setString(1, userId); + stmt.setString(2, studyGroup); + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + roles.add(new RoleAndGroup(rs.getInt(1), rs.getString(2), rs.getInt(3))); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + if (res.resultException != null) { + throw new DsmInternalError("Error getting study groups for user", res.resultException); + } + return (List) res.resultValue; + } - UserDto userDto = res.get(); + protected static int verifyRole(String role, int groupId) { + // TODO: Currently we do not track the valid roles for a group, but verify with + // groupId once we do -DC + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLE)) { + stmt.setString(1, role); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); - // assure role is for this study group + if (res.resultException != null) { + String msg = String.format("Error getting role %s for study group, ID: %d", role, groupId); + throw new DsmInternalError(msg, res.resultException); + } - int userId = userDto.getId(); - // insert + if (res.resultValue == null) { + throw new DSMBadRequestException("Invalid role for study group: " + role); + } + return (int) res.resultValue; } - private boolean operatorIsAdmin(String operatorId, int studyGroupId) { + protected static int verifyStudyGroup(String studyGroup) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP)) { + stmt.setString(1, studyGroup); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + + if (res.resultException != null) { + String msg = String.format("Error getting study group %s", studyGroup); + throw new DsmInternalError(msg, res.resultException); + } + + if (res.resultValue == null) { + throw new DSMBadRequestException("Invalid study group: " + studyGroup); + } + return (int) res.resultValue; + } + + protected static int getUserByEmailAndGroup(String email, int groupId) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_BY_EMAIL_AND_GROUP_ID)) { + stmt.setInt(1, groupId); + stmt.setString(2, email); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + + if (res.resultException != null) { + String msg = String.format("Error getting user %s for study group, ID: %d", email, groupId); + throw new DsmInternalError(msg, res.resultException); + } + + if (res.resultValue == null) { + throw new DSMBadRequestException("Invalid user for study group: " + email); + } + return (int) res.resultValue; + // TODO: see if access_user.is_active is used -DC + } + + protected static int getUserRole(int userId, int roleId, int groupId) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_ROLE)) { + stmt.setInt(1, userId); + stmt.setInt(2, roleId); + stmt.setInt(3, groupId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + + if (res.resultException != null) { + String msg = String.format("Error getting user role for study group: userId=%d, roleId=%d, groupO=Id=%d", + userId, roleId, groupId); + throw new DsmInternalError(msg, res.resultException); + } + + if (res.resultValue == null) { + return -1; + } + return (int) res.resultValue; + } + + protected static boolean addUserRole(int userId, int roleId, int groupId) throws Exception { + // since we do not have a key constraint for duplicate entries we need to check first + int userRoleId = getUserRole(userId, roleId, groupId); + if (userRoleId != -1) { + return false; + } + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER_ROLE)) { + stmt.setInt(1, userId); + stmt.setInt(2, roleId); + stmt.setInt(3, groupId); + int result = stmt.executeUpdate(); + if (result != 1) { + dbVals.resultException = new DsmInternalError("Error adding user to role. Result count was " + result); + } + } catch (SQLException ex) { + dbVals.resultException = ex; + } + return dbVals; + }); + + if (res.resultException != null) { + throw res.resultException; + } return true; } - /** - * Validate study group and assure role is valid for study group - * @return study group ID - * @throws DSMBadRequestException if study group is invalid or role is not valid for group - */ - private int getStudyGroup(String studyGroup, int roleId) { - return 1; + protected static int addRole(String role, int groupId) throws Exception { + // TODO: Currently we do not track the valid roles for a group, but enroll with + // groupId once we do -DC + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_ROLE, Statement.RETURN_GENERATED_KEYS)) { + stmt.setString(1, role); + int result = stmt.executeUpdate(); + if (result != 1) { + dbVals.resultException = new DsmInternalError("Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException ex) { + dbVals.resultException = ex; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error adding role " + role, res.resultException); + } + return (int) res.resultValue; + } + + protected static int addStudyGroup(String groupName) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_GROUP, Statement.RETURN_GENERATED_KEYS)) { + stmt.setString(1, groupName); + int result = stmt.executeUpdate(); + if (result != 1) { + dbVals.resultException = new DsmInternalError("Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException ex) { + dbVals.resultException = ex; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error adding study group " + groupName, res.resultException); + } + return (int) res.resultValue; + } + + protected static int deleteUserRole(int userId, int roleId) { + SimpleResult results = inTransaction(conn -> { + SimpleResult res = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_ROLE)) { + stmt.setInt(1, userId); + stmt.setInt(2, roleId); + res.resultValue = stmt.executeUpdate(); + } catch (SQLException ex) { + res.resultException = ex; + } + return res; + }); + if (results.resultException != null) { + String msg = String.format("Error deleting user role: userId=%d, roleId=%d", userId, roleId); + throw new DsmInternalError(msg, results.resultException); + } + return (int) results.resultValue; + } + + protected static int deleteStudyGroup(int groupId) { + SimpleResult results = inTransaction(conn -> { + SimpleResult res = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_GROUP)) { + stmt.setInt(1, groupId); + res.resultValue = stmt.executeUpdate(); + } catch (SQLException ex) { + res.resultException = ex; + } + return res; + }); + if (results.resultException != null) { + String msg = String.format("Error deleting study group: groupId=%d", groupId); + throw new DsmInternalError(msg, results.resultException); + } + return (int) results.resultValue; + } + + private static class RoleAndGroup { + public final int roleId; + public final String roleName; + public final int groupId; + + public RoleAndGroup(int roleId, String roleName, int groupId) { + this.roleId = roleId; + this.roleName = roleName; + this.groupId = groupId; + } } } diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java new file mode 100644 index 0000000000..85efa2b3ec --- /dev/null +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -0,0 +1,118 @@ +package org.broadinstitute.dsm.service.admin; + +import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; +import org.broadinstitute.dsm.DbTxnBaseTest; +import org.broadinstitute.dsm.db.dao.user.UserDao; +import org.broadinstitute.dsm.db.dto.user.UserDto; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +@Slf4j +public class UserAdminServiceTest extends DbTxnBaseTest { + + private static final Map> createdUserRoles = new HashMap<>(); + private static final String TEST_GROUP = "test_group"; + private static int studyGroupId; + + @BeforeClass + public static void setup() { + studyGroupId = UserAdminService.addStudyGroup(TEST_GROUP); + } + + @AfterClass + public static void tearDown() { + UserDao userDao = new UserDao(); + for (var entry: createdUserRoles.entrySet()) { + int userId = entry.getKey(); + List userRoles = entry.getValue(); + for (int userRole: userRoles) { + UserAdminService.deleteUserRole(userId, userRole); + } + userDao.delete(userId); + } + UserAdminService.deleteStudyGroup(studyGroupId); + } + + private int createUser(String email, int roleId) { + String name = email.split("@")[0]; + UserDto userDto = new UserDto(); + userDto.setName(name); + userDto.setEmail(email); + UserDao userDao = new UserDao(); + int userId = userDao.create(userDto); + List roleIds = List.of(roleId); + createdUserRoles.put(userId, roleIds); + return userId; + } + + private void addUserRole(int userId, int roleId, int groupId) throws Exception { + UserAdminService.addUserRole(userId, roleId, groupId); + List roleIds = createdUserRoles.get(userId); + List newRoleIds = new ArrayList<>(roleIds); + newRoleIds.add(roleId); + createdUserRoles.put(userId, newRoleIds); + } + + @Test + public void testVerifyRole() { + int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + Assert.assertTrue(roleId > 0); + } + + @Test + public void testVerifyOperatorForGroup() { + int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + Assert.assertTrue(roleId > 0); + int userId = createUser("test.admin@study.org", roleId); + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + try { + UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("No roles found")); + } + + try { + addUserRole(userId, roleId, groupId); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + try { + UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("does not have administrator privileges")); + } + + int adminRoleId = UserAdminService.verifyRole("study_admin", -1); + try { + addUserRole(userId, adminRoleId, groupId); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + try { + UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); + } + } + + @Test + public void testGetUserByEmailAndGroup() { + int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + Assert.assertTrue(roleId > 0); + String email = "testUser@study.org"; + int userId = createUser("testUser@study.org", roleId); + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + UserAdminService.getUserByEmailAndGroup(email, groupId); + } +} From 70711a6a5193d93b39304fb07977fec225962575 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 21 Jun 2023 23:46:46 -0400 Subject: [PATCH 08/35] WIP --- .../dsm/service/admin/UserAdminService.java | 18 ++++++++---------- .../service/admin/UserAdminServiceTest.java | 14 +++++++++++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 003589719e..7a5889eca5 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -50,11 +50,8 @@ public class UserAdminService { private static final String SQL_SELECT_GROUP = "SELECT dg.group_id FROM ddp_group dg WHERE dg.name = ?"; - private static final String SQL_SELECT_USER_BY_EMAIL_AND_GROUP_ID = - "select distinct(au.user_id), au.name FROM access_user au " - + "JOIN access_user_role_group aurg on aurg.user_id = au.user_id " - + "JOIN ddp_group dg on aurg.group_id = ? " - + "WHERE au.email = ?"; + private static final String SQL_SELECT_USER_BY_EMAIL = + "SELECT au.user_id, au.name FROM access_user au WHERE au.email = ?"; private static final String SQL_INSERT_USER_ROLE = "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; @@ -96,7 +93,7 @@ public void addUserToRole(AddUserRoleRequest req) { int roleId = verifyRole(role, groupId); - int userId = getUserByEmailAndGroup(email, groupId); + int userId = getUserByEmail(email, groupId); try { addUserRole(userId, roleId, groupId); @@ -219,12 +216,13 @@ protected static int verifyStudyGroup(String studyGroup) { return (int) res.resultValue; } - protected static int getUserByEmailAndGroup(String email, int groupId) { + protected static int getUserByEmail(String email, int groupId) { + // TODO: Currently we do not track the valid roles for a group, but get by + // groupId once we do -DC SimpleResult res = inTransaction(conn -> { SimpleResult dbVals = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_BY_EMAIL_AND_GROUP_ID)) { - stmt.setInt(1, groupId); - stmt.setString(2, email); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_BY_EMAIL)) { + stmt.setString(1, email); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { dbVals.resultValue = rs.getInt(1); diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 85efa2b3ec..535dbd343b 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -49,7 +49,10 @@ private int createUser(String email, int roleId) { userDto.setEmail(email); UserDao userDao = new UserDao(); int userId = userDao.create(userDto); - List roleIds = List.of(roleId); + List roleIds = new ArrayList<>(); + if (roleId != -1) { + roleIds.add(roleId); + } createdUserRoles.put(userId, roleIds); return userId; } @@ -111,8 +114,13 @@ public void testGetUserByEmailAndGroup() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); Assert.assertTrue(roleId > 0); String email = "testUser@study.org"; - int userId = createUser("testUser@study.org", roleId); + int userId = createUser("testUser@study.org", -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - UserAdminService.getUserByEmailAndGroup(email, groupId); + try { + int id = UserAdminService.getUserByEmail(email, groupId); + Assert.assertEquals(userId, id); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserByEmail: " + getStackTrace(e)); + } } } From f1a4e7aa139d5767715086ddec4d92799532646d Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 22 Jun 2023 10:39:28 -0400 Subject: [PATCH 09/35] WIP --- .../dsm/service/admin/UserAdminService.java | 13 ++++-- .../service/admin/UserAdminServiceTest.java | 42 +++++++++++++++++-- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 7a5889eca5..2938ff7e87 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -276,15 +276,15 @@ protected static int getUserRole(int userId, int roleId, int groupId) { return (int) res.resultValue; } - protected static boolean addUserRole(int userId, int roleId, int groupId) throws Exception { + protected static int addUserRole(int userId, int roleId, int groupId) throws Exception { // since we do not have a key constraint for duplicate entries we need to check first int userRoleId = getUserRole(userId, roleId, groupId); if (userRoleId != -1) { - return false; + return userRoleId; } SimpleResult res = inTransaction(conn -> { SimpleResult dbVals = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER_ROLE)) { + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER_ROLE, Statement.RETURN_GENERATED_KEYS)) { stmt.setInt(1, userId); stmt.setInt(2, roleId); stmt.setInt(3, groupId); @@ -292,6 +292,11 @@ protected static boolean addUserRole(int userId, int roleId, int groupId) throws if (result != 1) { dbVals.resultException = new DsmInternalError("Error adding user to role. Result count was " + result); } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } } catch (SQLException ex) { dbVals.resultException = ex; } @@ -301,7 +306,7 @@ protected static boolean addUserRole(int userId, int roleId, int groupId) throws if (res.resultException != null) { throw res.resultException; } - return true; + return (int) res.resultValue; } protected static int addRole(String role, int groupId) throws Exception { diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 535dbd343b..40c641a05f 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -57,12 +57,13 @@ private int createUser(String email, int roleId) { return userId; } - private void addUserRole(int userId, int roleId, int groupId) throws Exception { - UserAdminService.addUserRole(userId, roleId, groupId); + private int addUserRole(int userId, int roleId, int groupId) throws Exception { + int userRoleId = UserAdminService.addUserRole(userId, roleId, groupId); List roleIds = createdUserRoles.get(userId); List newRoleIds = new ArrayList<>(roleIds); newRoleIds.add(roleId); createdUserRoles.put(userId, newRoleIds); + return userRoleId; } @Test @@ -75,7 +76,7 @@ public void testVerifyRole() { public void testVerifyOperatorForGroup() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); Assert.assertTrue(roleId > 0); - int userId = createUser("test.admin@study.org", roleId); + int userId = createUser("test_admin1@study.org", roleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); @@ -114,7 +115,7 @@ public void testGetUserByEmailAndGroup() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); Assert.assertTrue(roleId > 0); String email = "testUser@study.org"; - int userId = createUser("testUser@study.org", -1); + int userId = createUser(email, -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { int id = UserAdminService.getUserByEmail(email, groupId); @@ -123,4 +124,37 @@ public void testGetUserByEmailAndGroup() { Assert.fail("Exception from UserAdminService.getUserByEmail: " + getStackTrace(e)); } } + + @Test + public void testAddUserToRole() { + int operatorId = createAdminUser("test_admin2@study.org"); + String role = "upload_onc_history"; + int roleId = UserAdminService.verifyRole(role, -1); + Assert.assertTrue(roleId > 0); + String email = "testUser2@study.org"; + int userId = createUser(email, roleId); + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + AddUserRoleRequest req = new AddUserRoleRequest(email, role, TEST_GROUP); + + UserAdminService service = new UserAdminService(Integer.toString(operatorId)); + try { + service.addUserToRole(req); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserToRole: " + getStackTrace(e)); + } + int userRoleId = UserAdminService.getUserRole(userId, roleId, groupId); + Assert.assertNotEquals(-1, userRoleId); + } + + private int createAdminUser(String email) { + int adminRoleId = UserAdminService.verifyRole("study_admin", -1); + int userId = createUser(email, adminRoleId); + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + try { + return addUserRole(userId, adminRoleId, groupId); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + return -1; + } } From 094b1629143f3b1504b485dfbdc01759846d666e Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 22 Jun 2023 13:08:55 -0400 Subject: [PATCH 10/35] WIP --- .../dsm/service/admin/UserAdminService.java | 3 ++- .../dsm/service/admin/UserAdminServiceTest.java | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 2938ff7e87..5f5c4711e9 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -127,7 +127,8 @@ protected static int verifyOperatorForGroup(String operatorId, String studyGroup List roles = getRolesForUser(operatorId, studyGroup); if (roles.isEmpty()) { - throw new DSMBadRequestException("No roles found for operator: " + operatorId); + String msg = String.format("No roles found for operator %s and group %s", operatorId, studyGroup); + throw new DSMBadRequestException(msg); } List roleNames = roles.stream().map(r -> r.roleName).collect(Collectors.toList()); if (!roleNames.contains(STUDY_ADMIN_ROLE)) { diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 40c641a05f..2d6c16e417 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -128,13 +128,18 @@ public void testGetUserByEmailAndGroup() { @Test public void testAddUserToRole() { int operatorId = createAdminUser("test_admin2@study.org"); + try { + UserAdminService.verifyOperatorForGroup(Integer.toString(operatorId), TEST_GROUP); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); + } String role = "upload_onc_history"; int roleId = UserAdminService.verifyRole(role, -1); Assert.assertTrue(roleId > 0); String email = "testUser2@study.org"; int userId = createUser(email, roleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - AddUserRoleRequest req = new AddUserRoleRequest(email, role, TEST_GROUP); + AddUserRoleRequest req = new AddUserRoleRequest(email, TEST_GROUP, role); UserAdminService service = new UserAdminService(Integer.toString(operatorId)); try { @@ -151,10 +156,11 @@ private int createAdminUser(String email) { int userId = createUser(email, adminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - return addUserRole(userId, adminRoleId, groupId); + addUserRole(userId, adminRoleId, groupId); + log.info("Created admin user wih id {}", userId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } - return -1; + return userId; } } From 97b5ee77c08ee34a282a0b38d3252885246e643b Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 22 Jun 2023 14:35:43 -0400 Subject: [PATCH 11/35] WIP --- .../org/broadinstitute/dsm/service/admin/UserAdminService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 5f5c4711e9..4f1105f2b4 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -42,7 +42,7 @@ public class UserAdminService { + "JOIN access_role ar on ar.role_id = aurg.role_id " + "JOIN ddp_group dg on dg.group_id = aurg.group_id " + "JOIN access_user au on au.user_id = aurg.user_id " - + "WHERE au.user_id = ? AND dg.name = ? AND ar.name = ?"; + + "WHERE au.user_id = ? AND ar.role_id = ? AND dg.group_id = ?"; private static final String SQL_SELECT_ROLE = "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; From c9c94fcaf551b16280d40497433449311f1d3212 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 22 Jun 2023 23:22:50 -0400 Subject: [PATCH 12/35] WIP --- .../src/main/java/org/broadinstitute/dsm/DSMServer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 8484e9b523..d5e2851018 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -122,6 +122,7 @@ import org.broadinstitute.dsm.route.TriggerSurveyRoute; import org.broadinstitute.dsm.route.UserSettingRoute; import org.broadinstitute.dsm.route.ViewFilterRoute; +import org.broadinstitute.dsm.route.admin.AddUserRoleRoute; import org.broadinstitute.dsm.route.admin.RegisterParticipantRoute; import org.broadinstitute.dsm.route.dashboard.NewDashboardRoute; import org.broadinstitute.dsm.route.familymember.AddFamilyMemberRoute; @@ -926,6 +927,9 @@ private void setupMiscellaneousRoutes() { RegisterParticipantRoute registerParticipantRoute = new RegisterParticipantRoute(); post(uiRoot + RoutePath.REGISTER_PARTICIPANT, registerParticipantRoute, new JsonTransformer()); + + AddUserRoleRoute userRoleRoute = new AddUserRoleRoute(); + post(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); } private void setupSomaticUploadRoutes(@NonNull Config cfg) { From e432b5f349ec586a0e02e271c9a50f44cf4548c4 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 22 Jun 2023 23:33:22 -0400 Subject: [PATCH 13/35] WIP --- .../src/main/java/org/broadinstitute/dsm/statics/RoutePath.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index acb2590096..d256d6dd52 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -96,6 +96,7 @@ public class RoutePath { public static final String GET_PARTICIPANT = "getParticipant"; public static final String GET_PARTICIPANT_DATA = "getParticipantData"; public static final String REGISTER_PARTICIPANT = "registerParticipant"; + public static final String USER_ROLE = "userRole"; public static final String TISSUE_LIST = "tissueList"; public static final String NDI_REQUEST = "ndiRequest"; public static final String ABSTRACTION_FORM_CONTROLS = "abstractionformcontrols"; From e87acd14589478cb4371d740f01bacbb53dbf5c9 Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 23 Jun 2023 23:04:16 -0400 Subject: [PATCH 14/35] WIP --- .../dsm/db/dao/user/UserDao.java | 3 +- .../dsm/db/dto/user/UserDto.java | 4 ++ .../dsm/route/admin/AddUserRoleRoute.java | 2 +- .../dsm/service/admin/AddUserRoleRequest.java | 8 ++- .../dsm/service/admin/UserAdminService.java | 52 +++++++++----- .../service/admin/UserAdminServiceTest.java | 71 ++++++++++++++----- 6 files changed, 99 insertions(+), 41 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index 87d1761f8e..e75aa9d937 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -18,7 +18,7 @@ public class UserDao implements Dao { public static final String NAME = "name"; public static final String EMAIL = "email"; public static final String PHONE_NUMBER = "phone_number"; - private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email) VALUES (?,?)"; + private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, is_active) VALUES (?,?,?)"; private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM access_user WHERE user_id = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = "SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.email = ?"; @@ -83,6 +83,7 @@ public int create(UserDto userDto) { try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, PreparedStatement.RETURN_GENERATED_KEYS)) { stmt.setString(1, userDto.getName().orElse("")); stmt.setString(2, userDto.getEmail().orElse("")); + stmt.setInt(3, userDto.getIsActive().orElse(0)); stmt.executeUpdate(); try (ResultSet rs = stmt.getGeneratedKeys()) { if (rs.next()) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java index f083ff95dc..e3e4a397dc 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java @@ -11,6 +11,7 @@ public class UserDto { private String name; private String email; private String phoneNumber; + private Integer isActive; public UserDto(int id, String name, String email, String phoneNumber) { this.id = id; @@ -38,4 +39,7 @@ public Optional getEmail() { public Optional getPhoneNumber() { return Optional.ofNullable(phoneNumber); } + public Optional getIsActive() { + return Optional.ofNullable(isActive); + } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java index d5affb528c..f3db599e90 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java @@ -37,7 +37,7 @@ public Object processRequest(Request request, Response response, String userId) UserAdminService adminService = new UserAdminService(userId); try { - adminService.addUserToRole(req); + adminService.addUserToRoles(req); } catch (DSMBadRequestException e) { response.status(400); return e.getMessage(); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java index 576d913fdf..3b88daa10a 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java @@ -1,5 +1,7 @@ package org.broadinstitute.dsm.service.admin; +import java.util.List; + import lombok.Data; @Data @@ -7,12 +9,12 @@ public class AddUserRoleRequest { private final String email; private final String studyGroup; - private final String role; + private final List roles; - public AddUserRoleRequest(String email, String studyGroup, String role) { + public AddUserRoleRequest(String email, String studyGroup, List roles) { this.email = email; this.studyGroup = studyGroup; - this.role = role; + this.roles = roles; } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 4f1105f2b4..0849847615 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -72,7 +72,7 @@ public UserAdminService(String operatorId) { this.operatorId = operatorId; } - public void addUserToRole(AddUserRoleRequest req) { + public void addUserToRoles(AddUserRoleRequest req) { // TODO: determine if operator can admin this study group user @@ -84,22 +84,34 @@ public void addUserToRole(AddUserRoleRequest req) { if (StringUtils.isBlank(email)) { throw new DSMBadRequestException("Invalid user email: blank"); } - String role = req.getRole(); - if (StringUtils.isBlank(role)) { - throw new DSMBadRequestException("Invalid role: blank"); + List roles = req.getRoles(); + if (roles.isEmpty()) { + throw new DSMBadRequestException("Invalid roles: empty"); } - int groupId = verifyOperatorForGroup(operatorId, group); - - int roleId = verifyRole(role, groupId); + int adminId; + try { + adminId = Integer.parseInt(operatorId); + } catch (NumberFormatException e) { + throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); + } + int groupId = verifyOperatorForGroup(adminId, group); int userId = getUserByEmail(email, groupId); - try { - addUserRole(userId, roleId, groupId); - } catch (Exception e) { - String msg = String.format("Error adding user %s to role %s", email, role); - throw new DsmInternalError(msg, e); + for (String role : roles) { + if (StringUtils.isBlank(role)) { + throw new DsmInternalError("Invalid role: blank"); + } + int roleId = verifyRole(role, groupId); + try { + addUserRole(userId, roleId, groupId); + String msg = String.format("Set up role %s for user %s in study group %s", role, email, group); + log.info(msg); + } catch (Exception e) { + String msg = String.format("Error adding user %s to role %s", email, role); + throw new DsmInternalError(msg, e); + } } } @@ -113,7 +125,13 @@ public void addStudyRole(AddStudyRoleRequest req) { throw new DSMBadRequestException("Invalid role: blank"); } - int groupId = verifyOperatorForGroup(operatorId, group); + int adminId; + try { + adminId = Integer.parseInt(operatorId); + } catch (NumberFormatException e) { + throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); + } + int groupId = verifyOperatorForGroup(adminId, group); try { addRole(role, groupId); @@ -123,7 +141,7 @@ public void addStudyRole(AddStudyRoleRequest req) { } } - protected static int verifyOperatorForGroup(String operatorId, String studyGroup) { + protected static int verifyOperatorForGroup(int operatorId, String studyGroup) { List roles = getRolesForUser(operatorId, studyGroup); if (roles.isEmpty()) { @@ -137,12 +155,12 @@ protected static int verifyOperatorForGroup(String operatorId, String studyGroup return roles.get(0).groupId; } - protected static List getRolesForUser(String userId, String studyGroup) { + protected static List getRolesForUser(int userId, String studyGroup) { SimpleResult res = inTransaction(conn -> { List roles = new ArrayList<>(); SimpleResult dbVals = new SimpleResult(roles); try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID)) { - stmt.setString(1, userId); + stmt.setInt(1, userId); stmt.setString(2, studyGroup); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { @@ -401,7 +419,7 @@ protected static int deleteStudyGroup(int groupId) { return (int) results.resultValue; } - private static class RoleAndGroup { + protected static class RoleAndGroup { public final int roleId; public final String roleName; public final int groupId; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 2d6c16e417..b1e95d8374 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -42,13 +42,8 @@ public static void tearDown() { UserAdminService.deleteStudyGroup(studyGroupId); } - private int createUser(String email, int roleId) { - String name = email.split("@")[0]; - UserDto userDto = new UserDto(); - userDto.setName(name); - userDto.setEmail(email); - UserDao userDao = new UserDao(); - int userId = userDao.create(userDto); + private int createTestUser(String email, int roleId) { + int userId = createUser(email); List roleIds = new ArrayList<>(); if (roleId != -1) { roleIds.add(roleId); @@ -57,13 +52,27 @@ private int createUser(String email, int roleId) { return userId; } + private int createUser(String email) { + String name = email.split("@")[0]; + UserDto userDto = new UserDto(); + userDto.setName(name); + userDto.setEmail(email); + userDto.setIsActive(1); + UserDao userDao = new UserDao(); + return userDao.create(userDto); + } + private int addUserRole(int userId, int roleId, int groupId) throws Exception { int userRoleId = UserAdminService.addUserRole(userId, roleId, groupId); + addRoleForUser(roleId, userId); + return userRoleId; + } + + private void addRoleForUser(int roleId, int userId) { List roleIds = createdUserRoles.get(userId); List newRoleIds = new ArrayList<>(roleIds); newRoleIds.add(roleId); createdUserRoles.put(userId, newRoleIds); - return userRoleId; } @Test @@ -76,10 +85,10 @@ public void testVerifyRole() { public void testVerifyOperatorForGroup() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); Assert.assertTrue(roleId > 0); - int userId = createUser("test_admin1@study.org", roleId); + int userId = createTestUser("test_admin1@study.org", roleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("No roles found")); @@ -91,7 +100,7 @@ public void testVerifyOperatorForGroup() { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } try { - UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("does not have administrator privileges")); @@ -104,7 +113,7 @@ public void testVerifyOperatorForGroup() { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } try { - UserAdminService.verifyOperatorForGroup(Integer.toString(userId), TEST_GROUP); + UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } @@ -115,7 +124,7 @@ public void testGetUserByEmailAndGroup() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); Assert.assertTrue(roleId > 0); String email = "testUser@study.org"; - int userId = createUser(email, -1); + int userId = createTestUser(email, -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { int id = UserAdminService.getUserByEmail(email, groupId); @@ -126,34 +135,41 @@ public void testGetUserByEmailAndGroup() { } @Test - public void testAddUserToRole() { + public void testAddUserToRoles() { int operatorId = createAdminUser("test_admin2@study.org"); try { - UserAdminService.verifyOperatorForGroup(Integer.toString(operatorId), TEST_GROUP); + UserAdminService.verifyOperatorForGroup(operatorId, TEST_GROUP); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } String role = "upload_onc_history"; int roleId = UserAdminService.verifyRole(role, -1); Assert.assertTrue(roleId > 0); + String role2 = "upload_ror_file"; + int roleId2 = UserAdminService.verifyRole(role2, -1); + Assert.assertTrue(roleId2 > 0); String email = "testUser2@study.org"; - int userId = createUser(email, roleId); + int userId = createTestUser(email, roleId); + addRoleForUser(roleId2, userId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - AddUserRoleRequest req = new AddUserRoleRequest(email, TEST_GROUP, role); + List roles = List.of(role, role2); + AddUserRoleRequest req = new AddUserRoleRequest(email, TEST_GROUP, roles); UserAdminService service = new UserAdminService(Integer.toString(operatorId)); try { - service.addUserToRole(req); + service.addUserToRoles(req); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserToRole: " + getStackTrace(e)); } int userRoleId = UserAdminService.getUserRole(userId, roleId, groupId); Assert.assertNotEquals(-1, userRoleId); + int userRoleId2 = UserAdminService.getUserRole(userId, roleId2, groupId); + Assert.assertNotEquals(-1, userRoleId2); } private int createAdminUser(String email) { int adminRoleId = UserAdminService.verifyRole("study_admin", -1); - int userId = createUser(email, adminRoleId); + int userId = createTestUser(email, adminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { addUserRole(userId, adminRoleId, groupId); @@ -163,4 +179,21 @@ private int createAdminUser(String email) { } return userId; } + + private void setUserRoles(String email, List roles, String studyGroup) throws Exception { + try { + int userId = UserAdminService.getUserByEmail(email, -1); + int groupId = UserAdminService.verifyStudyGroup(studyGroup); + + for (String role : roles) { + int roleId = UserAdminService.verifyRole(role, -1); + UserAdminService.addUserRole(userId, roleId, groupId); + String msg = String.format("Set up role %s for user %s in study group %s", role, email, studyGroup); + log.info(msg); + } + } catch (Exception e) { + log.error("Exception in setUserRoles: " + getStackTrace(e)); + throw e; + } + } } From 75db27ec00341f50bca8b3363586cf9a66b09062 Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 27 Jun 2023 15:25:38 -0400 Subject: [PATCH 15/35] Add support for multiple users per request --- .../dsm/service/admin/AddUserRoleRequest.java | 6 ++-- .../dsm/service/admin/UserAdminService.java | 28 +++++++++++-------- .../service/admin/UserAdminServiceTest.java | 26 ++++++++++++----- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java index 3b88daa10a..656ef3d0dc 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java @@ -7,12 +7,12 @@ @Data public class AddUserRoleRequest { - private final String email; + private final List users; private final String studyGroup; private final List roles; - public AddUserRoleRequest(String email, String studyGroup, List roles) { - this.email = email; + public AddUserRoleRequest(List users, String studyGroup, List roles) { + this.users = users; this.studyGroup = studyGroup; this.roles = roles; } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 0849847615..2de0a5af05 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -73,16 +73,13 @@ public UserAdminService(String operatorId) { } public void addUserToRoles(AddUserRoleRequest req) { - - // TODO: determine if operator can admin this study group user - - String group = req.getStudyGroup(); - if (StringUtils.isBlank(group)) { + String studyGroup = req.getStudyGroup(); + if (StringUtils.isBlank(studyGroup)) { throw new DSMBadRequestException("Invalid study group: blank"); } - String email = req.getEmail(); - if (StringUtils.isBlank(email)) { - throw new DSMBadRequestException("Invalid user email: blank"); + List users = req.getUsers(); + if (users.isEmpty()) { + throw new DSMBadRequestException("Invalid users: empty"); } List roles = req.getRoles(); if (roles.isEmpty()) { @@ -95,8 +92,17 @@ public void addUserToRoles(AddUserRoleRequest req) { } catch (NumberFormatException e) { throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); } - int groupId = verifyOperatorForGroup(adminId, group); + int groupId = verifyOperatorForGroup(adminId, studyGroup); + + for (String userEmail: users) { + if (StringUtils.isBlank(userEmail)) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + addUserRoles(userEmail, roles, groupId, studyGroup); + } + } + protected void addUserRoles(String email, List roles, int groupId, String studyGroup) { int userId = getUserByEmail(email, groupId); for (String role : roles) { @@ -106,10 +112,10 @@ public void addUserToRoles(AddUserRoleRequest req) { int roleId = verifyRole(role, groupId); try { addUserRole(userId, roleId, groupId); - String msg = String.format("Set up role %s for user %s in study group %s", role, email, group); + String msg = String.format("Set up role %s for user %s in study group %s", role, email, studyGroup); log.info(msg); } catch (Exception e) { - String msg = String.format("Error adding user %s to role %s", email, role); + String msg = String.format("Error adding user %s to role %s for study group %s", email, role, studyGroup); throw new DsmInternalError(msg, e); } } diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index b1e95d8374..8297d03033 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -148,12 +148,19 @@ public void testAddUserToRoles() { String role2 = "upload_ror_file"; int roleId2 = UserAdminService.verifyRole(role2, -1); Assert.assertTrue(roleId2 > 0); - String email = "testUser2@study.org"; - int userId = createTestUser(email, roleId); - addRoleForUser(roleId2, userId); - int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); List roles = List.of(role, role2); - AddUserRoleRequest req = new AddUserRoleRequest(email, TEST_GROUP, roles); + + String user1 = "testUser2@study.org"; + int userId1 = createTestUser(user1, roleId); + addRoleForUser(roleId2, userId1); + String user2 = "testUser3@study.org"; + int userId2 = createTestUser(user2, roleId); + addRoleForUser(roleId2, userId2); + + List users = List.of(user1, user2); + + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + AddUserRoleRequest req = new AddUserRoleRequest(users, TEST_GROUP, roles); UserAdminService service = new UserAdminService(Integer.toString(operatorId)); try { @@ -161,10 +168,15 @@ public void testAddUserToRoles() { } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserToRole: " + getStackTrace(e)); } - int userRoleId = UserAdminService.getUserRole(userId, roleId, groupId); + int userRoleId = UserAdminService.getUserRole(userId1, roleId, groupId); Assert.assertNotEquals(-1, userRoleId); - int userRoleId2 = UserAdminService.getUserRole(userId, roleId2, groupId); + int userRoleId2 = UserAdminService.getUserRole(userId1, roleId2, groupId); Assert.assertNotEquals(-1, userRoleId2); + + int user2RoleId = UserAdminService.getUserRole(userId2, roleId, groupId); + Assert.assertNotEquals(-1, user2RoleId); + int user2RoleId2 = UserAdminService.getUserRole(userId2, roleId2, groupId); + Assert.assertNotEquals(-1, user2RoleId2); } private int createAdminUser(String email) { From 9d25d0831c7161832328445758a456540f855341 Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 27 Jun 2023 17:18:46 -0400 Subject: [PATCH 16/35] WIP --- .../org/broadinstitute/dsm/DSMServer.java | 4 +- .../dsm/route/admin/AddUserRoleRoute.java | 68 ------------------- .../dsm/route/admin/UserRoleRoute.java | 65 ++++++++++++++++++ .../dsm/service/admin/UserAdminService.java | 44 +++++++++--- ...rRoleRequest.java => UserRoleRequest.java} | 5 +- .../broadinstitute/dsm/statics/RoutePath.java | 2 +- .../service/admin/UserAdminServiceTest.java | 2 +- 7 files changed, 107 insertions(+), 83 deletions(-) delete mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java rename pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/{AddUserRoleRequest.java => UserRoleRequest.java} (71%) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index e7c1797de6..070c568d10 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -120,7 +120,7 @@ import org.broadinstitute.dsm.route.TriggerSurveyRoute; import org.broadinstitute.dsm.route.UserSettingRoute; import org.broadinstitute.dsm.route.ViewFilterRoute; -import org.broadinstitute.dsm.route.admin.AddUserRoleRoute; +import org.broadinstitute.dsm.route.admin.UserRoleRoute; import org.broadinstitute.dsm.route.admin.RegisterParticipantRoute; import org.broadinstitute.dsm.route.dashboard.NewDashboardRoute; import org.broadinstitute.dsm.route.familymember.AddFamilyMemberRoute; @@ -917,7 +917,7 @@ private void setupMiscellaneousRoutes() { RegisterParticipantRoute registerParticipantRoute = new RegisterParticipantRoute(); post(uiRoot + RoutePath.REGISTER_PARTICIPANT, registerParticipantRoute, new JsonTransformer()); - AddUserRoleRoute userRoleRoute = new AddUserRoleRoute(); + UserRoleRoute userRoleRoute = new UserRoleRoute(); post(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java deleted file mode 100644 index f3db599e90..0000000000 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/AddUserRoleRoute.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.broadinstitute.dsm.route.admin; - -import com.google.gson.Gson; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.broadinstitute.dsm.exception.DSMBadRequestException; -import org.broadinstitute.dsm.exception.DsmInternalError; -import org.broadinstitute.dsm.security.RequestHandler; -import org.broadinstitute.dsm.service.admin.AddUserRoleRequest; -import org.broadinstitute.dsm.service.admin.UserAdminService; -import org.broadinstitute.lddp.handlers.util.Result; -import spark.Request; -import spark.Response; - -@Slf4j -public class AddUserRoleRoute extends RequestHandler { - - @Override - public Object processRequest(Request request, Response response, String userId) { - - String body = request.body(); - if (StringUtils.isBlank(body)) { - response.status(400); - return "Request body is blank"; - } - - AddUserRoleRequest req; - try { - req = new Gson().fromJson(body, AddUserRoleRequest.class); - log.info("TEMP: AddUserRoleRequest {}", req); - } catch (Exception e) { - log.info("Invalid request format for {}", body); - response.status(400); - return "Invalid request format"; - } - - UserAdminService adminService = new UserAdminService(userId); - - try { - adminService.addUserToRoles(req); - } catch (DSMBadRequestException e) { - response.status(400); - return e.getMessage(); - } catch (DsmInternalError e) { - log.error("Error adding user to role: {}", e.getMessage()); - response.status(500); - return "Internal error. Contact development team"; - } catch (Exception e) { - log.error("Error adding user to role: {}", e.getMessage()); - response.status(500); - return e.getMessage(); - } - - return new Result(200); - } - /* - SELECT @uemail := ""; - -SELECT @uid := user_id FROM access_user WHERE email COLLATE utf8mb4_general_ci = @uemail; - - -SELECT @gid := group_id from ddp_group where name COLLATE utf8mb4_general_ci = "pecgs" ; - -INSERT INTO `dev_dsm_db`.`access_user_role_group` (`user_id`, `role_id`, `group_id`) - SELECT @uid, role_id, @gid FROM access_role WHERE name in ('onc_history_upload', 'upload_ror_file'); - */ - -} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java new file mode 100644 index 0000000000..6b64d474e9 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java @@ -0,0 +1,65 @@ +package org.broadinstitute.dsm.route.admin; + +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.broadinstitute.dsm.exception.DSMBadRequestException; +import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.service.admin.UserRoleRequest; +import org.broadinstitute.dsm.service.admin.UserAdminService; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.lddp.handlers.util.Result; +import spark.Request; +import spark.Response; + +@Slf4j +public class UserRoleRoute extends RequestHandler { + + @Override + public Object processRequest(Request request, Response response, String userId) { + + String body = request.body(); + if (StringUtils.isBlank(body)) { + response.status(400); + return "Request body is blank"; + } + + UserRoleRequest req; + try { + req = new Gson().fromJson(body, UserRoleRequest.class); + log.info("TEMP: UserRoleRequest {}", req); + } catch (Exception e) { + log.info("Invalid request format for {}", body); + response.status(400); + return "Invalid request format"; + } + + UserAdminService adminService = new UserAdminService(userId); + + if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { + try { + adminService.addUserToRoles(req); + } catch (DSMBadRequestException e) { + response.status(400); + return e.getMessage(); + } catch (DsmInternalError e) { + log.error("Error adding users to roles: {}", e.getMessage()); + response.status(500); + return "Internal error. Contact development team"; + } catch (Exception e) { + log.error("Error adding users to roles: {}", e.getMessage()); + response.status(500); + return e.getMessage(); + } + //else if (request.requestMethod().equals(RoutePath.RequestMethod.DELETE.toString())) { + } else { + String msg = "Invalid HTTP method for UserRoleRoute"; + log.error(msg); + response.status(500); + return msg; + } + + return new Result(200); + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 2de0a5af05..4844e4b541 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -72,7 +72,7 @@ public UserAdminService(String operatorId) { this.operatorId = operatorId; } - public void addUserToRoles(AddUserRoleRequest req) { + public void addUserToRoles(UserRoleRequest req) { String studyGroup = req.getStudyGroup(); if (StringUtils.isBlank(studyGroup)) { throw new DSMBadRequestException("Invalid study group: blank"); @@ -86,13 +86,7 @@ public void addUserToRoles(AddUserRoleRequest req) { throw new DSMBadRequestException("Invalid roles: empty"); } - int adminId; - try { - adminId = Integer.parseInt(operatorId); - } catch (NumberFormatException e) { - throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); - } - int groupId = verifyOperatorForGroup(adminId, studyGroup); + int groupId = verifyOperatorAndGroup(studyGroup); for (String userEmail: users) { if (StringUtils.isBlank(userEmail)) { @@ -121,6 +115,40 @@ protected void addUserRoles(String email, List roles, int groupId, Strin } } + public void removeUserFromRoles(UserRoleRequest req) { + String studyGroup = req.getStudyGroup(); + if (StringUtils.isBlank(studyGroup)) { + throw new DSMBadRequestException("Invalid study group: blank"); + } + List users = req.getUsers(); + if (users.isEmpty()) { + throw new DSMBadRequestException("Invalid users: empty"); + } + List roles = req.getRoles(); + if (roles.isEmpty()) { + throw new DSMBadRequestException("Invalid roles: empty"); + } + + int groupId = verifyOperatorAndGroup(studyGroup); + + for (String userEmail: users) { + if (StringUtils.isBlank(userEmail)) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + addUserRoles(userEmail, roles, groupId, studyGroup); + } + } + + protected int verifyOperatorAndGroup(String studyGroup) { + int adminId; + try { + adminId = Integer.parseInt(operatorId); + } catch (NumberFormatException e) { + throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); + } + return verifyOperatorForGroup(adminId, studyGroup); + } + public void addStudyRole(AddStudyRoleRequest req) { String group = req.getStudyGroup(); if (StringUtils.isBlank(group)) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java similarity index 71% rename from pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java rename to pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java index 656ef3d0dc..ce46a98e0d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java @@ -5,16 +5,15 @@ import lombok.Data; @Data -public class AddUserRoleRequest { +public class UserRoleRequest { private final List users; private final String studyGroup; private final List roles; - public AddUserRoleRequest(List users, String studyGroup, List roles) { + public UserRoleRequest(List users, String studyGroup, List roles) { this.users = users; this.studyGroup = studyGroup; this.roles = roles; } - } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index 25ac0f3d3a..c8d16a870d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -136,7 +136,7 @@ public static String getRealm(Request request) { } public enum RequestMethod { - GET, PATCH, POST, PUT + GET, PATCH, POST, PUT, DELETE } public static final class GAE { diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 8297d03033..a9ca63449c 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -160,7 +160,7 @@ public void testAddUserToRoles() { List users = List.of(user1, user2); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - AddUserRoleRequest req = new AddUserRoleRequest(users, TEST_GROUP, roles); + UserRoleRequest req = new UserRoleRequest(users, TEST_GROUP, roles); UserAdminService service = new UserAdminService(Integer.toString(operatorId)); try { From 1e2ab65e48c438942a2219ff5a4ec37e9f27d02b Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 30 Jun 2023 08:47:37 -0400 Subject: [PATCH 17/35] Add user create and remove support --- .../org/broadinstitute/dsm/DSMServer.java | 4 + .../dsm/db/dao/user/UserDao.java | 12 +- .../dsm/route/admin/UserRoute.java | 78 ++++++++ ...RoleRequest.java => StudyRoleRequest.java} | 5 +- .../dsm/service/admin/UserAdminService.java | 186 ++++++++++++++---- .../dsm/service/admin/UserRequest.java | 26 +++ .../dsm/service/admin/UserRoleResponse.java | 27 +++ .../broadinstitute/dsm/statics/RoutePath.java | 1 + .../service/admin/UserAdminServiceTest.java | 52 ++++- 9 files changed, 344 insertions(+), 47 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java rename pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/{AddStudyRoleRequest.java => StudyRoleRequest.java} (68%) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 070c568d10..33d8528dcb 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -919,6 +919,10 @@ private void setupMiscellaneousRoutes() { UserRoleRoute userRoleRoute = new UserRoleRoute(); post(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); + + UserRoleRoute userRoute = new UserRoleRoute(); + post(uiRoot + RoutePath.USER, userRoute, new JsonTransformer()); + delete(uiRoot + RoutePath.USER, userRoute, new JsonTransformer()); } private void setupSomaticUploadRoutes(@NonNull Config cfg) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index e75aa9d937..bd6603e6c5 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -10,6 +10,7 @@ import lombok.NonNull; import org.broadinstitute.dsm.db.dao.Dao; import org.broadinstitute.dsm.db.dto.user.UserDto; +import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.lddp.db.SimpleResult; public class UserDao implements Dao { @@ -18,7 +19,7 @@ public class UserDao implements Dao { public static final String NAME = "name"; public static final String EMAIL = "email"; public static final String PHONE_NUMBER = "phone_number"; - private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, is_active) VALUES (?,?,?)"; + private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, phone_number, is_active) VALUES (?,?,?,?)"; private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM access_user WHERE user_id = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = "SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.email = ?"; @@ -83,7 +84,8 @@ public int create(UserDto userDto) { try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, PreparedStatement.RETURN_GENERATED_KEYS)) { stmt.setString(1, userDto.getName().orElse("")); stmt.setString(2, userDto.getEmail().orElse("")); - stmt.setInt(3, userDto.getIsActive().orElse(0)); + stmt.setString(3, userDto.getPhoneNumber().orElse("")); + stmt.setInt(4, userDto.getIsActive().orElse(0)); stmt.executeUpdate(); try (ResultSet rs = stmt.getGeneratedKeys()) { if (rs.next()) { @@ -96,8 +98,7 @@ public int create(UserDto userDto) { return execResult; }); if (results.resultException != null) { - throw new RuntimeException("Error inserting user with " - + userDto.getEmail(), results.resultException); + throw new DsmInternalError("Error inserting user " + userDto.getEmail(), results.resultException); } return (int) results.resultValue; } @@ -115,8 +116,7 @@ public int delete(int id) { return execResult; }); if (results.resultException != null) { - throw new RuntimeException("Error deleting user with " - + id, results.resultException); + throw new DsmInternalError("Error deleting user with ID " + id, results.resultException); } return (int) results.resultValue; } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java new file mode 100644 index 0000000000..77cc6d1cdf --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java @@ -0,0 +1,78 @@ +package org.broadinstitute.dsm.route.admin; + +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.broadinstitute.dsm.exception.DSMBadRequestException; +import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.service.admin.UserAdminService; +import org.broadinstitute.dsm.service.admin.UserRequest; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.lddp.handlers.util.Result; +import spark.Request; +import spark.Response; + +@Slf4j +public class UserRoute extends RequestHandler { + + @Override + public Object processRequest(Request request, Response response, String userId) { + + String body = request.body(); + if (StringUtils.isBlank(body)) { + response.status(400); + return "Request body is blank"; + } + + UserRequest req; + try { + req = new Gson().fromJson(body, UserRequest.class); + } catch (Exception e) { + log.info("Invalid request format for {}", body); + response.status(400); + return "Invalid request format"; + } + + UserAdminService adminService = new UserAdminService(userId); + + if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { + try { + adminService.createUser(req); + } catch (DSMBadRequestException e) { + response.status(400); + return e.getMessage(); + } catch (DsmInternalError e) { + log.error("Error adding user: {}", e.getMessage()); + response.status(500); + return "Internal error. Contact development team"; + } catch (Exception e) { + log.error("Error adding user: {}", e.getMessage()); + response.status(500); + return e.getMessage(); + } + } else if (request.requestMethod().equals(RoutePath.RequestMethod.DELETE.toString())) { + try { + adminService.removeUser(req); + } catch (DSMBadRequestException e) { + response.status(400); + return e.getMessage(); + } catch (DsmInternalError e) { + log.error("Error removing user: {}", e.getMessage()); + response.status(500); + return "Internal error. Contact development team"; + } catch (Exception e) { + log.error("Error removing user: {}", e.getMessage()); + response.status(500); + return e.getMessage(); + } + } else { + String msg = "Invalid HTTP method for UserRoute"; + log.error(msg); + response.status(500); + return msg; + } + + return new Result(200); + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java similarity index 68% rename from pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java rename to pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java index db7f172397..d5e8715d47 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddStudyRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java @@ -2,12 +2,13 @@ import lombok.Data; + @Data -public class AddStudyRoleRequest { +public class StudyRoleRequest { private final String studyGroup; private final String role; - public AddStudyRoleRequest(String studyGroup, String role) { + public StudyRoleRequest(String studyGroup, String role) { this.studyGroup = studyGroup; this.role = role; } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 4844e4b541..f0cfbc8817 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.lddp.db.SimpleResult; @@ -21,7 +22,7 @@ public class UserAdminService { private final String operatorId; - private static final String STUDY_ADMIN_ROLE = "study_admin"; + private static final String USER_ADMIN_ROLE = "study_admin"; private static final String SQL_SELECT_ASSIGNEE = "SELECT user.user_id, user.name, user.email FROM access_user_role_group roleGroup, access_user user, access_role role, " @@ -30,12 +31,16 @@ public class UserAdminService { + " AND realmGroup.ddp_group_id = ddp_group.group_id AND ddp_group.group_id = roleGroup.group_id " + "AND role.name = \"mr_request\" AND realm.instance_name = ?"; + private static final String SQL_SELECT_ROLES_FOR_USER = + "SELECT ar.role_id, ar.name FROM access_role ar " + + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + + "WHERE aurg.user_id = ? AND aurg.group_id = ?"; + private static final String SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID = "SELECT ar.role_id, ar.name, dg.group_id FROM access_role ar " + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + "JOIN ddp_group dg on dg.group_id = aurg.group_id " - + "JOIN access_user au on au.user_id = aurg.user_id " - + "WHERE au.user_id = ? AND dg.name = ?"; + + "WHERE aurg.user_id = ? AND dg.name = ?"; private static final String SQL_SELECT_USER_ROLE = "SELECT aurg.user_role_group_id FROM access_user_role_group aurg " @@ -56,8 +61,11 @@ public class UserAdminService { private static final String SQL_INSERT_USER_ROLE = "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; + private static final String SQL_DELETE_USER_ROLES = + "DELETE FROM access_user_role_group WHERE user_id = ?"; + private static final String SQL_DELETE_USER_ROLE = - "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ?"; + "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ? AND group_id = ?"; private static final String SQL_INSERT_ROLE = "INSERT INTO access_role SET name = ?"; @@ -68,31 +76,24 @@ public class UserAdminService { private static final String SQL_DELETE_GROUP = "DELETE FROM ddp_group WHERE group_id = ?"; + private static final String SQL_INSERT_USER = + "INSERT INTO ddp_group SET name = ?"; + + public UserAdminService(String operatorId) { this.operatorId = operatorId; } public void addUserToRoles(UserRoleRequest req) { - String studyGroup = req.getStudyGroup(); - if (StringUtils.isBlank(studyGroup)) { - throw new DSMBadRequestException("Invalid study group: blank"); - } - List users = req.getUsers(); - if (users.isEmpty()) { - throw new DSMBadRequestException("Invalid users: empty"); - } - List roles = req.getRoles(); - if (roles.isEmpty()) { - throw new DSMBadRequestException("Invalid roles: empty"); - } + validateRoleRequest(req, true); - int groupId = verifyOperatorAndGroup(studyGroup); + int groupId = verifyOperatorAndGroup(req.getStudyGroup()); - for (String userEmail: users) { + for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - addUserRoles(userEmail, roles, groupId, studyGroup); + addUserRoles(userEmail, req.getRoles(), groupId, req.getStudyGroup()); } } @@ -116,27 +117,92 @@ protected void addUserRoles(String email, List roles, int groupId, Strin } public void removeUserFromRoles(UserRoleRequest req) { - String studyGroup = req.getStudyGroup(); - if (StringUtils.isBlank(studyGroup)) { - throw new DSMBadRequestException("Invalid study group: blank"); - } - List users = req.getUsers(); - if (users.isEmpty()) { - throw new DSMBadRequestException("Invalid users: empty"); + validateRoleRequest(req, true); + + int groupId = verifyOperatorAndGroup(req.getStudyGroup()); + + for (String userEmail: req.getUsers()) { + if (StringUtils.isBlank(userEmail)) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + removeUserRoles(userEmail, req.getRoles(), groupId, req.getStudyGroup()); } - List roles = req.getRoles(); - if (roles.isEmpty()) { - throw new DSMBadRequestException("Invalid roles: empty"); + } + + protected void removeUserRoles(String email, List roles, int groupId, String studyGroup) { + // TODO loop is similar to above + int userId = getUserByEmail(email, groupId); + + for (String role : roles) { + if (StringUtils.isBlank(role)) { + throw new DsmInternalError("Invalid role: blank"); + } + int roleId = verifyRole(role, groupId); + try { + deleteUserRole(userId, roleId, groupId); + String msg = String.format("Removed role %s for user %s in study group %s", role, email, studyGroup); + log.info(msg); + } catch (Exception e) { + String msg = String.format("Error removing user %s from role %s for study group %s", email, role, studyGroup); + throw new DsmInternalError(msg, e); + } } + } + public UserRoleResponse getUserRoles(UserRoleRequest req) { + validateRoleRequest(req, false); - int groupId = verifyOperatorAndGroup(studyGroup); + int groupId = verifyOperatorAndGroup(req.getStudyGroup()); - for (String userEmail: users) { + UserRoleResponse userRoleResponse = new UserRoleResponse(); + for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - addUserRoles(userEmail, roles, groupId, studyGroup); + int userId = getUserByEmail(userEmail, groupId); + + userRoleResponse.addUserRoles(userEmail, getRolesForUser(userId, groupId)); } + return userRoleResponse; + } + + protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { + if (StringUtils.isBlank(req.getStudyGroup())) { + throw new DSMBadRequestException("Invalid study group: blank"); + } + if (req.getUsers().isEmpty()) { + throw new DSMBadRequestException("Invalid users: empty"); + } + if (req.getRoles().isEmpty()) { + if (includesRoles) { + throw new DSMBadRequestException("Invalid roles: empty"); + } + } else if (!includesRoles) { + throw new DSMBadRequestException("Invalid roles: should be empty"); + } + } + + public void createUser(UserRequest req) { + if (StringUtils.isBlank(req.getName())) { + throw new DSMBadRequestException("Invalid user name: blank"); + } + if (StringUtils.isBlank(req.getEmail())) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + // TODO: Currently we do not assign users to study groups, but when we do validate UserRequest.studyGroup -DC + + UserDao userDao = new UserDao(); + userDao.create(req.asUserDto()); + } + + public void removeUser(UserRequest req) { + if (StringUtils.isBlank(req.getEmail())) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + + int userId = getUserByEmail(req.getEmail(), -1); + deleteUserRoles(userId); + UserDao userDao = new UserDao(); + userDao.delete(userId); } protected int verifyOperatorAndGroup(String studyGroup) { @@ -149,7 +215,7 @@ protected int verifyOperatorAndGroup(String studyGroup) { return verifyOperatorForGroup(adminId, studyGroup); } - public void addStudyRole(AddStudyRoleRequest req) { + public void addStudyRole(StudyRoleRequest req) { String group = req.getStudyGroup(); if (StringUtils.isBlank(group)) { throw new DSMBadRequestException("Invalid study group: blank"); @@ -177,19 +243,43 @@ public void addStudyRole(AddStudyRoleRequest req) { protected static int verifyOperatorForGroup(int operatorId, String studyGroup) { - List roles = getRolesForUser(operatorId, studyGroup); + List roles = getRolesAndGroupForUser(operatorId, studyGroup); if (roles.isEmpty()) { String msg = String.format("No roles found for operator %s and group %s", operatorId, studyGroup); throw new DSMBadRequestException(msg); } List roleNames = roles.stream().map(r -> r.roleName).collect(Collectors.toList()); - if (!roleNames.contains(STUDY_ADMIN_ROLE)) { + if (!roleNames.contains(USER_ADMIN_ROLE)) { throw new DSMBadRequestException("Operator does not have administrator privileges: " + operatorId); } return roles.get(0).groupId; } - protected static List getRolesForUser(int userId, String studyGroup) { + protected static List getRolesForUser(int userId, int groupId) { + SimpleResult res = inTransaction(conn -> { + List roles = new ArrayList<>(); + SimpleResult dbVals = new SimpleResult(roles); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_USER)) { + stmt.setInt(1, userId); + stmt.setInt(2, groupId); + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + roles.add(rs.getString(2)); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error getting roles groups for user", res.resultException); + } + return (List) res.resultValue; + } + + protected static List getRolesAndGroupForUser(int userId, String studyGroup) { SimpleResult res = inTransaction(conn -> { List roles = new ArrayList<>(); SimpleResult dbVals = new SimpleResult(roles); @@ -416,12 +506,32 @@ protected static int addStudyGroup(String groupName) { return (int) res.resultValue; } - protected static int deleteUserRole(int userId, int roleId) { + protected static int deleteUserRole(int userId, int roleId, int groupId) { SimpleResult results = inTransaction(conn -> { SimpleResult res = new SimpleResult(); try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_ROLE)) { stmt.setInt(1, userId); stmt.setInt(2, roleId); + stmt.setInt(3, groupId); + res.resultValue = stmt.executeUpdate(); + } catch (SQLException ex) { + res.resultException = ex; + } + return res; + }); + if (results.resultException != null) { + String msg = String.format("Error deleting user role: userId=%d, roleId=%d, groupId=%d", + userId, roleId, groupId); + throw new DsmInternalError(msg, results.resultException); + } + return (int) results.resultValue; + } + + protected static int deleteUserRoles(int userId) { + SimpleResult results = inTransaction(conn -> { + SimpleResult res = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_ROLES)) { + stmt.setInt(1, userId); res.resultValue = stmt.executeUpdate(); } catch (SQLException ex) { res.resultException = ex; @@ -429,7 +539,7 @@ protected static int deleteUserRole(int userId, int roleId) { return res; }); if (results.resultException != null) { - String msg = String.format("Error deleting user role: userId=%d, roleId=%d", userId, roleId); + String msg = String.format("Error deleting user roles: userId=%d", userId); throw new DsmInternalError(msg, results.resultException); } return (int) results.resultValue; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java new file mode 100644 index 0000000000..ee0b1bb120 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java @@ -0,0 +1,26 @@ +package org.broadinstitute.dsm.service.admin; + +import lombok.Data; +import org.broadinstitute.dsm.db.dto.user.UserDto; + +@Data +public class UserRequest { + + private final String name; + private final String email; + private final String phone; + private final String studyGroup; + + // TODO extend to include optional roles for user -DC + + public UserRequest(String name, String email, String phone, String studyGroup) { + this.name = name; + this.email = email; + this.phone = phone; + this.studyGroup = studyGroup; + } + + public UserDto asUserDto() { + return new UserDto(-1, name, email, phone); + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java new file mode 100644 index 0000000000..2ef2dc212a --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java @@ -0,0 +1,27 @@ +package org.broadinstitute.dsm.service.admin; + +import java.util.ArrayList; +import java.util.List; + +public class UserRoleResponse { + + private List userRolesList; + + public UserRoleResponse() { + this.userRolesList = new ArrayList<>(); + } + + public void addUserRoles(String userEmail, List roles) { + this.userRolesList.add(new UserRoles(userEmail, roles)); + } + + public static class UserRoles { + public final String user; + public final List roles; + + public UserRoles(String user, List roles) { + this.user = user; + this.roles = roles; + } + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index c8d16a870d..4f9676020a 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -96,6 +96,7 @@ public class RoutePath { public static final String GET_PARTICIPANT = "getParticipant"; public static final String GET_PARTICIPANT_DATA = "getParticipantData"; public static final String REGISTER_PARTICIPANT = "registerParticipant"; + public static final String USER = "user"; public static final String USER_ROLE = "userRole"; public static final String TISSUE_LIST = "tissueList"; public static final String NDI_REQUEST = "ndiRequest"; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index a9ca63449c..eeb68807d9 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -35,7 +35,7 @@ public static void tearDown() { int userId = entry.getKey(); List userRoles = entry.getValue(); for (int userRole: userRoles) { - UserAdminService.deleteUserRole(userId, userRole); + UserAdminService.deleteUserRole(userId, userRole, studyGroupId); } userDao.delete(userId); } @@ -179,6 +179,56 @@ public void testAddUserToRoles() { Assert.assertNotEquals(-1, user2RoleId2); } + @Test + public void testAddAndRemoveUser() { + int operatorId = createAdminUser("test_admin3@study.org"); + int groupId = -1; + try { + groupId = UserAdminService.verifyOperatorForGroup(operatorId, TEST_GROUP); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); + } + + String email = "testUser4@study.org"; + UserRequest req = new UserRequest("testUser4", email, null, TEST_GROUP); + + UserAdminService service = new UserAdminService(Integer.toString(operatorId)); + try { + service.createUser(req); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.createUser: " + getStackTrace(e)); + } + + int userId = -1; + try { + userId = UserAdminService.getUserByEmail(email, -1); + } catch (Exception e) { + Assert.fail("Exception verifying UserAdminService.createUser: " + getStackTrace(e)); + } + + int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + Assert.assertTrue(roleId > 0); + + try { + UserAdminService.addUserRole(userId, roleId, groupId); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + + try { + service.removeUser(req); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.removeUser: " + getStackTrace(e)); + } + + try { + UserAdminService.getUserByEmail(email, -1); + Assert.fail("UserAdminService.removeUser failed to remove user"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid user")); + } + } + private int createAdminUser(String email) { int adminRoleId = UserAdminService.verifyRole("study_admin", -1); int userId = createTestUser(email, adminRoleId); From 9c28c2c9946c6cef12a8ab16fed009cb6560ab77 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 5 Jul 2023 10:21:06 -0400 Subject: [PATCH 18/35] WIP --- .../dsm/service/admin/UserAdminService.java | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index f0cfbc8817..fde3830a86 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -24,13 +24,6 @@ public class UserAdminService { private static final String USER_ADMIN_ROLE = "study_admin"; - private static final String SQL_SELECT_ASSIGNEE = - "SELECT user.user_id, user.name, user.email FROM access_user_role_group roleGroup, access_user user, access_role role, " - + "ddp_group, ddp_instance_group realmGroup, ddp_instance realm WHERE roleGroup.user_id = user.user_id AND roleGroup" - + ".role_id = role.role_id AND realm.ddp_instance_id = realmGroup.ddp_instance_id" - + " AND realmGroup.ddp_group_id = ddp_group.group_id AND ddp_group.group_id = roleGroup.group_id " - + "AND role.name = \"mr_request\" AND realm.instance_name = ?"; - private static final String SQL_SELECT_ROLES_FOR_USER = "SELECT ar.role_id, ar.name FROM access_role ar " + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " @@ -79,6 +72,11 @@ public class UserAdminService { private static final String SQL_INSERT_USER = "INSERT INTO ddp_group SET name = ?"; + public static final String SQL_SELECT_GROUP_FOR_REALM = + "SELECT dg.group_id, dg.name from ddp_group dg " + + "JOIN ddp_instance_group dig on dig.ddp_group_id = dg.group_id " + + "JOIN ddp_instance di on di.ddp_instance_id = dig.ddp_instance_id " + + "WHERE di.instance_name = ?"; public UserAdminService(String operatorId) { this.operatorId = operatorId; @@ -148,6 +146,7 @@ protected void removeUserRoles(String email, List roles, int groupId, St } } } + public UserRoleResponse getUserRoles(UserRoleRequest req) { validateRoleRequest(req, false); @@ -165,6 +164,28 @@ public UserRoleResponse getUserRoles(UserRoleRequest req) { return userRoleResponse; } + public NameAndId getStudyGroupForRealm(String realm) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP_FOR_REALM)) { + stmt.setString(1, realm); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + dbVals.resultValue = new NameAndId(rs.getString(2), rs.getInt(1)); + } + } + } catch (SQLException e) { + dbVals.resultException = e; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error getting roles groups for user", res.resultException); + } + return (NameAndId) res.resultValue; + } + protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { if (StringUtils.isBlank(req.getStudyGroup())) { throw new DSMBadRequestException("Invalid study group: blank"); @@ -243,16 +264,16 @@ public void addStudyRole(StudyRoleRequest req) { protected static int verifyOperatorForGroup(int operatorId, String studyGroup) { - List roles = getRolesAndGroupForUser(operatorId, studyGroup); + List roles = getRolesAndGroupForUser(operatorId, studyGroup); if (roles.isEmpty()) { String msg = String.format("No roles found for operator %s and group %s", operatorId, studyGroup); throw new DSMBadRequestException(msg); } - List roleNames = roles.stream().map(r -> r.roleName).collect(Collectors.toList()); + List roleNames = roles.stream().map(r -> r.name).collect(Collectors.toList()); if (!roleNames.contains(USER_ADMIN_ROLE)) { throw new DSMBadRequestException("Operator does not have administrator privileges: " + operatorId); } - return roles.get(0).groupId; + return roles.get(0).id; } protected static List getRolesForUser(int userId, int groupId) { @@ -279,16 +300,16 @@ protected static List getRolesForUser(int userId, int groupId) { return (List) res.resultValue; } - protected static List getRolesAndGroupForUser(int userId, String studyGroup) { + protected static List getRolesAndGroupForUser(int userId, String studyGroup) { SimpleResult res = inTransaction(conn -> { - List roles = new ArrayList<>(); + List roles = new ArrayList<>(); SimpleResult dbVals = new SimpleResult(roles); try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID)) { stmt.setInt(1, userId); stmt.setString(2, studyGroup); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { - roles.add(new RoleAndGroup(rs.getInt(1), rs.getString(2), rs.getInt(3))); + roles.add(new NameAndId(rs.getString(2), rs.getInt(3))); } } } catch (SQLException e) { @@ -300,7 +321,7 @@ protected static List getRolesAndGroupForUser(int userId, String s if (res.resultException != null) { throw new DsmInternalError("Error getting study groups for user", res.resultException); } - return (List) res.resultValue; + return (List) res.resultValue; } protected static int verifyRole(String role, int groupId) { @@ -563,15 +584,13 @@ protected static int deleteStudyGroup(int groupId) { return (int) results.resultValue; } - protected static class RoleAndGroup { - public final int roleId; - public final String roleName; - public final int groupId; + protected static class NameAndId { + public final String name; + public final int id; - public RoleAndGroup(int roleId, String roleName, int groupId) { - this.roleId = roleId; - this.roleName = roleName; - this.groupId = groupId; + public NameAndId(String name, int id) { + this.name = name; + this.id = id; } } } From 4e3583a10ac9a73a09c662993097b5716e390cf4 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 5 Jul 2023 15:44:05 -0400 Subject: [PATCH 19/35] WIP --- .../dsm/service/admin/UserAdminService.java | 8 +- .../dsm/service/admin/UserRoleResponse.java | 4 + .../service/admin/UserAdminServiceTest.java | 74 +++++++++++++++++-- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index fde3830a86..d95dc9dc1c 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; @@ -128,7 +129,7 @@ public void removeUserFromRoles(UserRoleRequest req) { } protected void removeUserRoles(String email, List roles, int groupId, String studyGroup) { - // TODO loop is similar to above + // TODO loop is similar to addUserRoles -DC int userId = getUserByEmail(email, groupId); for (String role : roles) { @@ -137,6 +138,7 @@ protected void removeUserRoles(String email, List roles, int groupId, St } int roleId = verifyRole(role, groupId); try { + // ignore failure to remove deleteUserRole(userId, roleId, groupId); String msg = String.format("Removed role %s for user %s in study group %s", role, email, studyGroup); log.info(msg); @@ -190,10 +192,10 @@ protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { if (StringUtils.isBlank(req.getStudyGroup())) { throw new DSMBadRequestException("Invalid study group: blank"); } - if (req.getUsers().isEmpty()) { + if (CollectionUtils.isEmpty(req.getUsers())) { throw new DSMBadRequestException("Invalid users: empty"); } - if (req.getRoles().isEmpty()) { + if (CollectionUtils.isEmpty(req.getRoles())) { if (includesRoles) { throw new DSMBadRequestException("Invalid roles: empty"); } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java index 2ef2dc212a..d897391d01 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java @@ -15,6 +15,10 @@ public void addUserRoles(String userEmail, List roles) { this.userRolesList.add(new UserRoles(userEmail, roles)); } + public List getUserRoles() { + return userRolesList; + } + public static class UserRoles { public final String user; public final List roles; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index eeb68807d9..392e875465 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -75,6 +75,13 @@ private void addRoleForUser(int roleId, int userId) { createdUserRoles.put(userId, newRoleIds); } + private void removeRoleForUser(int roleId, int userId) { + List roleIds = createdUserRoles.get(userId); + List newRoleIds = new ArrayList<>(roleIds); + newRoleIds.removeAll(List.of(roleId)); + createdUserRoles.put(userId, newRoleIds); + } + @Test public void testVerifyRole() { int roleId = UserAdminService.verifyRole("upload_onc_history", -1); @@ -142,19 +149,19 @@ public void testAddUserToRoles() { } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } - String role = "upload_onc_history"; - int roleId = UserAdminService.verifyRole(role, -1); - Assert.assertTrue(roleId > 0); + String role1 = "upload_onc_history"; + int roleId1 = UserAdminService.verifyRole(role1, -1); + Assert.assertTrue(roleId1 > 0); String role2 = "upload_ror_file"; int roleId2 = UserAdminService.verifyRole(role2, -1); Assert.assertTrue(roleId2 > 0); - List roles = List.of(role, role2); + List roles = List.of(role1, role2); String user1 = "testUser2@study.org"; - int userId1 = createTestUser(user1, roleId); + int userId1 = createTestUser(user1, roleId1); addRoleForUser(roleId2, userId1); String user2 = "testUser3@study.org"; - int userId2 = createTestUser(user2, roleId); + int userId2 = createTestUser(user2, roleId1); addRoleForUser(roleId2, userId2); List users = List.of(user1, user2); @@ -168,15 +175,66 @@ public void testAddUserToRoles() { } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserToRole: " + getStackTrace(e)); } - int userRoleId = UserAdminService.getUserRole(userId1, roleId, groupId); + int userRoleId = UserAdminService.getUserRole(userId1, roleId1, groupId); Assert.assertNotEquals(-1, userRoleId); int userRoleId2 = UserAdminService.getUserRole(userId1, roleId2, groupId); Assert.assertNotEquals(-1, userRoleId2); - int user2RoleId = UserAdminService.getUserRole(userId2, roleId, groupId); + int user2RoleId = UserAdminService.getUserRole(userId2, roleId1, groupId); Assert.assertNotEquals(-1, user2RoleId); int user2RoleId2 = UserAdminService.getUserRole(userId2, roleId2, groupId); Assert.assertNotEquals(-1, user2RoleId2); + + UserRoleRequest req2 = new UserRoleRequest(users, TEST_GROUP, List.of(role1)); + try { + service.removeUserFromRoles(req2); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); + } + + UserRoleRequest getReq = new UserRoleRequest(users, TEST_GROUP, null); + UserRoleResponse res = null; + try { + res = service.getUserRoles(getReq); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + } + + List userRoles = res.getUserRoles(); + for (UserRoleResponse.UserRoles resRoles: userRoles) { + Assert.assertTrue("RemoveUserFromRole removed wrong role", resRoles.roles.contains(role2)); + Assert.assertFalse("RemoveUserFromRole did not remove role", resRoles.roles.contains(role1)); + } + + // adjust cleanup + removeRoleForUser(roleId1, userId1); + removeRoleForUser(roleId1, userId2); + + UserRoleRequest req3 = new UserRoleRequest(List.of(user2), TEST_GROUP, List.of(role2)); + try { + service.removeUserFromRoles(req3); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); + } + + UserRoleRequest getReq2 = new UserRoleRequest(users, TEST_GROUP, null); + try { + res = service.getUserRoles(getReq2); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + } + + userRoles = res.getUserRoles(); + for (UserRoleResponse.UserRoles resRoles: userRoles) { + if (resRoles.user.equals(user1)) { + Assert.assertTrue(resRoles.roles.contains(role2)); + } else { + Assert.assertTrue("RemoveUserFromRole did not remove role", resRoles.roles.isEmpty()); + } + } + + // adjust cleanup + removeRoleForUser(roleId2, userId2); } @Test From 3c82589e45db65217fcee7ca202c5dc58b232c07 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 12 Jul 2023 14:46:25 -0400 Subject: [PATCH 20/35] WIP --- .../dsm/route/admin/UserRoleRoute.java | 4 +- .../dsm/route/admin/UserRoute.java | 5 +- .../dsm/service/admin/StudyRoleResponse.java | 11 ++ .../dsm/service/admin/UserAdminService.java | 104 ++++++++----- .../dsm/service/admin/UserRequest.java | 6 +- .../dsm/service/admin/UserRoleRequest.java | 4 +- .../broadinstitute/dsm/statics/RoutePath.java | 1 + .../service/admin/UserAdminServiceTest.java | 143 +++++++++++++++++- 8 files changed, 221 insertions(+), 57 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java index 6b64d474e9..f25b589abc 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java @@ -19,6 +19,8 @@ public class UserRoleRoute extends RequestHandler { @Override public Object processRequest(Request request, Response response, String userId) { + String studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + String body = request.body(); if (StringUtils.isBlank(body)) { response.status(400); @@ -35,7 +37,7 @@ public Object processRequest(Request request, Response response, String userId) return "Invalid request format"; } - UserAdminService adminService = new UserAdminService(userId); + UserAdminService adminService = new UserAdminService(userId, studyGroup); if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { try { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java index 77cc6d1cdf..594a3a1fe0 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java @@ -10,6 +10,7 @@ import org.broadinstitute.dsm.service.admin.UserRequest; import org.broadinstitute.dsm.statics.RoutePath; import org.broadinstitute.lddp.handlers.util.Result; +import spark.QueryParamsMap; import spark.Request; import spark.Response; @@ -19,6 +20,8 @@ public class UserRoute extends RequestHandler { @Override public Object processRequest(Request request, Response response, String userId) { + String studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + String body = request.body(); if (StringUtils.isBlank(body)) { response.status(400); @@ -34,7 +37,7 @@ public Object processRequest(Request request, Response response, String userId) return "Invalid request format"; } - UserAdminService adminService = new UserAdminService(userId); + UserAdminService adminService = new UserAdminService(userId, studyGroup); if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { try { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java new file mode 100644 index 0000000000..a2289700f4 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java @@ -0,0 +1,11 @@ +package org.broadinstitute.dsm.service.admin; + +import java.util.Map; + +public class StudyRoleResponse { + private Map studyRoles; + + public StudyRoleResponse(Map studyRoles) { + this.studyRoles = studyRoles; + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index d95dc9dc1c..3a1081cee0 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -8,6 +8,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -15,6 +16,7 @@ import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.dsm.statics.RoutePath; import org.broadinstitute.lddp.db.SimpleResult; import spark.utils.StringUtils; @@ -22,6 +24,7 @@ public class UserAdminService { private final String operatorId; + private final String studyGroup; private static final String USER_ADMIN_ROLE = "study_admin"; @@ -79,20 +82,27 @@ public class UserAdminService { + "JOIN ddp_instance di on di.ddp_instance_id = dig.ddp_instance_id " + "WHERE di.instance_name = ?"; - public UserAdminService(String operatorId) { + public UserAdminService(String operatorId, String studyGroup) { this.operatorId = operatorId; + this.studyGroup = studyGroup; + } + + public void getStudyRoles() { + + int groupId = verifyOperatorAndGroup(studyGroup); + throw new DsmInternalError("Not implemented"); } public void addUserToRoles(UserRoleRequest req) { validateRoleRequest(req, true); - int groupId = verifyOperatorAndGroup(req.getStudyGroup()); + int groupId = verifyOperatorAndGroup(studyGroup); for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - addUserRoles(userEmail, req.getRoles(), groupId, req.getStudyGroup()); + addUserRoles(userEmail, req.getRoles(), groupId, studyGroup); } } @@ -118,13 +128,13 @@ protected void addUserRoles(String email, List roles, int groupId, Strin public void removeUserFromRoles(UserRoleRequest req) { validateRoleRequest(req, true); - int groupId = verifyOperatorAndGroup(req.getStudyGroup()); + int groupId = verifyOperatorAndGroup(studyGroup); for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - removeUserRoles(userEmail, req.getRoles(), groupId, req.getStudyGroup()); + removeUserRoles(userEmail, req.getRoles(), groupId, studyGroup); } } @@ -152,7 +162,7 @@ protected void removeUserRoles(String email, List roles, int groupId, St public UserRoleResponse getUserRoles(UserRoleRequest req) { validateRoleRequest(req, false); - int groupId = verifyOperatorAndGroup(req.getStudyGroup()); + int groupId = verifyOperatorAndGroup(studyGroup); UserRoleResponse userRoleResponse = new UserRoleResponse(); for (String userEmail: req.getUsers()) { @@ -166,7 +176,39 @@ public UserRoleResponse getUserRoles(UserRoleRequest req) { return userRoleResponse; } - public NameAndId getStudyGroupForRealm(String realm) { + public static String getStudyGroup(Map queryParams) { + String studyGroup = null; + String realmGroup = null; + if (queryParams.containsKey(RoutePath.STUDY_GROUP)) { + studyGroup = queryParams.get(RoutePath.STUDY_GROUP)[0]; + } + if (queryParams.containsKey(RoutePath.REALM)) { + realmGroup = UserAdminService.getStudyGroupForRealm(queryParams.get(RoutePath.REALM)[0]); + } + if (realmGroup == null) { + if (studyGroup == null) { + throw new DSMBadRequestException("Invalid realm or study group"); + } + return studyGroup; + } else { + if (studyGroup != null && !studyGroup.equals(realmGroup)) { + String msg = String.format("Provided study group %s does not match provided realm %s", studyGroup, + queryParams.get(RoutePath.REALM)[0]); + throw new DSMBadRequestException(msg); + } + } + return realmGroup; + } + + protected static String getStudyGroupForRealm(String realm) { + NameAndId res = _getStudyGroupForRealm(realm); + if (res == null) { + throw new DSMBadRequestException("Invalid realm: " + realm); + } + return res.name; + } + + private static NameAndId _getStudyGroupForRealm(String realm) { SimpleResult res = inTransaction(conn -> { SimpleResult dbVals = new SimpleResult(); try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP_FOR_REALM)) { @@ -189,7 +231,7 @@ public NameAndId getStudyGroupForRealm(String realm) { } protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { - if (StringUtils.isBlank(req.getStudyGroup())) { + if (StringUtils.isBlank(studyGroup)) { throw new DSMBadRequestException("Invalid study group: blank"); } if (CollectionUtils.isEmpty(req.getUsers())) { @@ -239,7 +281,7 @@ protected int verifyOperatorAndGroup(String studyGroup) { } public void addStudyRole(StudyRoleRequest req) { - String group = req.getStudyGroup(); + String group = studyGroup; if (StringUtils.isBlank(group)) { throw new DSMBadRequestException("Invalid study group: blank"); } @@ -530,60 +572,42 @@ protected static int addStudyGroup(String groupName) { } protected static int deleteUserRole(int userId, int roleId, int groupId) { - SimpleResult results = inTransaction(conn -> { - SimpleResult res = new SimpleResult(); + return inTransaction(conn -> { try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_ROLE)) { stmt.setInt(1, userId); stmt.setInt(2, roleId); stmt.setInt(3, groupId); - res.resultValue = stmt.executeUpdate(); + return stmt.executeUpdate(); } catch (SQLException ex) { - res.resultException = ex; + String msg = String.format("Error deleting user role: userId=%d, roleId=%d, groupId=%d", + userId, roleId, groupId); + throw new DsmInternalError(msg, ex); } - return res; }); - if (results.resultException != null) { - String msg = String.format("Error deleting user role: userId=%d, roleId=%d, groupId=%d", - userId, roleId, groupId); - throw new DsmInternalError(msg, results.resultException); - } - return (int) results.resultValue; } protected static int deleteUserRoles(int userId) { - SimpleResult results = inTransaction(conn -> { - SimpleResult res = new SimpleResult(); + return inTransaction(conn -> { try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_ROLES)) { stmt.setInt(1, userId); - res.resultValue = stmt.executeUpdate(); + return stmt.executeUpdate(); } catch (SQLException ex) { - res.resultException = ex; + String msg = String.format("Error deleting user roles: userId=%d", userId); + throw new DsmInternalError(msg, ex); } - return res; }); - if (results.resultException != null) { - String msg = String.format("Error deleting user roles: userId=%d", userId); - throw new DsmInternalError(msg, results.resultException); - } - return (int) results.resultValue; } protected static int deleteStudyGroup(int groupId) { - SimpleResult results = inTransaction(conn -> { - SimpleResult res = new SimpleResult(); + return inTransaction(conn -> { try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_GROUP)) { stmt.setInt(1, groupId); - res.resultValue = stmt.executeUpdate(); + return stmt.executeUpdate(); } catch (SQLException ex) { - res.resultException = ex; + String msg = String.format("Error deleting study group: groupId=%d", groupId); + throw new DsmInternalError(msg, ex); } - return res; }); - if (results.resultException != null) { - String msg = String.format("Error deleting study group: groupId=%d", groupId); - throw new DsmInternalError(msg, results.resultException); - } - return (int) results.resultValue; } protected static class NameAndId { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java index ee0b1bb120..c9ef228f65 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java @@ -9,15 +9,13 @@ public class UserRequest { private final String name; private final String email; private final String phone; - private final String studyGroup; - // TODO extend to include optional roles for user -DC + // TODO extend to include roles for user -DC - public UserRequest(String name, String email, String phone, String studyGroup) { + public UserRequest(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; - this.studyGroup = studyGroup; } public UserDto asUserDto() { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java index ce46a98e0d..5d481036cc 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleRequest.java @@ -8,12 +8,10 @@ public class UserRoleRequest { private final List users; - private final String studyGroup; private final List roles; - public UserRoleRequest(List users, String studyGroup, List roles) { + public UserRoleRequest(List users, List roles) { this.users = users; - this.studyGroup = studyGroup; this.roles = roles; } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index 1bf899991d..445dc62e75 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -7,6 +7,7 @@ public class RoutePath { public static final String REALM = "realm"; + public static final String STUDY_GROUP = "studyGroup"; public static final String KIT_TYPE = "kitType"; public static final String UPLOAD_REASONS = "uploadReasons"; public static final String CARRIERS = "carriers"; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 392e875465..74bcff7d24 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -1,7 +1,12 @@ package org.broadinstitute.dsm.service.admin; import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; +import static org.broadinstitute.ddp.db.TransactionWrapper.inTransaction; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -11,6 +16,9 @@ import org.broadinstitute.dsm.DbTxnBaseTest; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.db.dto.user.UserDto; +import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.lddp.db.SimpleResult; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -21,11 +29,26 @@ public class UserAdminServiceTest extends DbTxnBaseTest { private static final Map> createdUserRoles = new HashMap<>(); private static final String TEST_GROUP = "test_group"; + private static final String TEST_INSTANCE = "test_instance"; private static int studyGroupId; + private static int ddpInstanceId; + + private static final String SQL_INSERT_DDP_INSTANCE = + "INSERT INTO ddp_instance SET instance_name = ?, is_active = 1, auth0_token = 1, migrated_ddp = 0"; + + private static final String SQL_INSERT_DDP_INSTANCE_GROUP = + "INSERT INTO ddp_instance_group SET ddp_instance_id = ?, ddp_group_id = ?"; + + private static final String SQL_DELETE_DDP_INSTANCE_GROUP = + "DELETE FROM ddp_instance_group WHERE ddp_instance_id = ?"; + + private static final String SQL_DELETE_DDP_INSTANCE = + "DELETE FROM ddp_instance WHERE ddp_instance_id = ?"; @BeforeClass public static void setup() { studyGroupId = UserAdminService.addStudyGroup(TEST_GROUP); + ddpInstanceId = createTestInstance(TEST_INSTANCE, studyGroupId); } @AfterClass @@ -39,6 +62,7 @@ public static void tearDown() { } userDao.delete(userId); } + deleteInstance(ddpInstanceId); UserAdminService.deleteStudyGroup(studyGroupId); } @@ -167,9 +191,9 @@ public void testAddUserToRoles() { List users = List.of(user1, user2); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - UserRoleRequest req = new UserRoleRequest(users, TEST_GROUP, roles); + UserRoleRequest req = new UserRoleRequest(users, roles); - UserAdminService service = new UserAdminService(Integer.toString(operatorId)); + UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { service.addUserToRoles(req); } catch (Exception e) { @@ -185,14 +209,14 @@ public void testAddUserToRoles() { int user2RoleId2 = UserAdminService.getUserRole(userId2, roleId2, groupId); Assert.assertNotEquals(-1, user2RoleId2); - UserRoleRequest req2 = new UserRoleRequest(users, TEST_GROUP, List.of(role1)); + UserRoleRequest req2 = new UserRoleRequest(users, List.of(role1)); try { service.removeUserFromRoles(req2); } catch (Exception e) { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } - UserRoleRequest getReq = new UserRoleRequest(users, TEST_GROUP, null); + UserRoleRequest getReq = new UserRoleRequest(users, null); UserRoleResponse res = null; try { res = service.getUserRoles(getReq); @@ -210,14 +234,14 @@ public void testAddUserToRoles() { removeRoleForUser(roleId1, userId1); removeRoleForUser(roleId1, userId2); - UserRoleRequest req3 = new UserRoleRequest(List.of(user2), TEST_GROUP, List.of(role2)); + UserRoleRequest req3 = new UserRoleRequest(List.of(user2), List.of(role2)); try { service.removeUserFromRoles(req3); } catch (Exception e) { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } - UserRoleRequest getReq2 = new UserRoleRequest(users, TEST_GROUP, null); + UserRoleRequest getReq2 = new UserRoleRequest(users, null); try { res = service.getUserRoles(getReq2); } catch (Exception e) { @@ -248,9 +272,9 @@ public void testAddAndRemoveUser() { } String email = "testUser4@study.org"; - UserRequest req = new UserRequest("testUser4", email, null, TEST_GROUP); + UserRequest req = new UserRequest("testUser4", email, null); - UserAdminService service = new UserAdminService(Integer.toString(operatorId)); + UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { service.createUser(req); } catch (Exception e) { @@ -287,6 +311,33 @@ public void testAddAndRemoveUser() { } } + @Test + public void testGetStudyGroup() { + Map qp = new HashMap<>(); + try { + UserAdminService.getStudyGroup(qp); + Assert.fail("Expecting exception from UserAdminService.getStudyGroup"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid")); + } + String[] groupName = {TEST_GROUP}; + String[] realmName = {TEST_INSTANCE}; + qp.put(RoutePath.STUDY_GROUP, groupName); + Assert.assertEquals(TEST_GROUP, UserAdminService.getStudyGroup(qp)); + qp.put(RoutePath.REALM, realmName); + Assert.assertEquals(TEST_GROUP, UserAdminService.getStudyGroup(qp)); + qp.remove(RoutePath.STUDY_GROUP); + Assert.assertEquals(TEST_GROUP, UserAdminService.getStudyGroup(qp)); + try { + String[] invalidGroupName = {"invalid_test_group"}; + qp.put(RoutePath.STUDY_GROUP, invalidGroupName); + UserAdminService.getStudyGroup(qp); + Assert.fail("Expecting exception from UserAdminService.getStudyGroup"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("does not match")); + } + } + private int createAdminUser(String email) { int adminRoleId = UserAdminService.verifyRole("study_admin", -1); int userId = createTestUser(email, adminRoleId); @@ -316,4 +367,80 @@ private void setUserRoles(String email, List roles, String studyGroup) t throw e; } } + + private static int createTestInstance(String instanceName, int studyGroupId) { + int instanceId = createInstance(instanceName); + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_DDP_INSTANCE_GROUP, Statement.RETURN_GENERATED_KEYS)) { + stmt.setInt(1, instanceId); + stmt.setInt(2, studyGroupId); + int result = stmt.executeUpdate(); + if (result != 1) { + dbVals.resultException = new DsmInternalError("Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException ex) { + dbVals.resultException = ex; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error adding DDP instance group " + instanceName, res.resultException); + } + return instanceId; + } + + private static int createInstance(String instanceName) { + SimpleResult res = inTransaction(conn -> { + SimpleResult dbVals = new SimpleResult(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_DDP_INSTANCE, Statement.RETURN_GENERATED_KEYS)) { + stmt.setString(1, instanceName); + int result = stmt.executeUpdate(); + if (result != 1) { + dbVals.resultException = new DsmInternalError("Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + dbVals.resultValue = rs.getInt(1); + } + } + } catch (SQLException ex) { + dbVals.resultException = ex; + } + return dbVals; + }); + + if (res.resultException != null) { + throw new DsmInternalError("Error adding DDP instance group " + instanceName, res.resultException); + } + return (int) res.resultValue; + } + + private static void deleteInstance(int instanceId) { + inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_DDP_INSTANCE_GROUP)) { + stmt.setInt(1, instanceId); + return stmt.executeUpdate(); + } catch (SQLException ex) { + String msg = String.format("Error deleting DDP instance group: ddpInstanceId=%d", instanceId); + throw new DsmInternalError(msg, ex); + } + }); + + inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_DDP_INSTANCE)) { + stmt.setInt(1, instanceId); + return stmt.executeUpdate(); + } catch (SQLException ex) { + String msg = String.format("Error deleting DDP instance: ddpInstanceId=%d", instanceId); + throw new DsmInternalError(msg, ex); + } + }); + } } From 915f834a4f2f3f8b74a621f4a8bbe77b52641676 Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 14 Jul 2023 11:55:41 -0400 Subject: [PATCH 21/35] WIP --- .../dsm/route/admin/UserRoute.java | 7 +- .../dsm/service/admin/AddUserRequest.java | 26 + .../dsm/service/admin/StudyRoleRequest.java | 20 +- .../dsm/service/admin/UserAdminService.java | 525 ++++++++++++------ .../dsm/service/admin/UserInfo.java | 29 + .../dsm/service/admin/UserRequest.java | 20 +- .../dsm/service/admin/UserRole.java | 13 + .../dsm/service/admin/UserRoleResponse.java | 26 +- .../service/admin/UserAdminServiceTest.java | 65 ++- 9 files changed, 480 insertions(+), 251 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java index 594a3a1fe0..9db8f136d7 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java @@ -7,10 +7,9 @@ import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.security.RequestHandler; import org.broadinstitute.dsm.service.admin.UserAdminService; -import org.broadinstitute.dsm.service.admin.UserRequest; +import org.broadinstitute.dsm.service.admin.AddUserRequest; import org.broadinstitute.dsm.statics.RoutePath; import org.broadinstitute.lddp.handlers.util.Result; -import spark.QueryParamsMap; import spark.Request; import spark.Response; @@ -28,9 +27,9 @@ public Object processRequest(Request request, Response response, String userId) return "Request body is blank"; } - UserRequest req; + AddUserRequest req; try { - req = new Gson().fromJson(body, UserRequest.class); + req = new Gson().fromJson(body, AddUserRequest.class); } catch (Exception e) { log.info("Invalid request format for {}", body); response.status(400); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java new file mode 100644 index 0000000000..31bacd61bb --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java @@ -0,0 +1,26 @@ +package org.broadinstitute.dsm.service.admin; + +import java.util.List; + +import lombok.Data; +import org.broadinstitute.dsm.db.dto.user.UserDto; + +@Data +public class AddUserRequest { + + private final String email; + private final String name; + private final String phone; + private final List roles; + + public AddUserRequest(String email, String name, String phone, List roles) { + this.email = email; + this.name = name; + this.phone = phone; + this.roles = roles; + } + + public UserDto asUserDto() { + return new UserDto(-1, name, email, phone); + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java index d5e8715d47..f9401d8fbf 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java @@ -1,15 +1,25 @@ package org.broadinstitute.dsm.service.admin; +import java.util.List; + import lombok.Data; @Data public class StudyRoleRequest { - private final String studyGroup; - private final String role; + private final List roles; + + public StudyRoleRequest(List roles) { + this.roles = roles; + } + + public static class RoleInfo { + public final String roleName; + public final String adminRoleName; - public StudyRoleRequest(String studyGroup, String role) { - this.studyGroup = studyGroup; - this.role = role; + public RoleInfo(String roleName, String adminRoleName) { + this.roleName = roleName; + this.adminRoleName = adminRoleName; + } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 3a1081cee0..3c95f2073c 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -6,9 +6,14 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Types; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -25,19 +30,38 @@ public class UserAdminService { private final String operatorId; private final String studyGroup; + protected static final String USER_ADMIN_ROLE = "study_user_admin"; + protected static final String PEPPER_ADMIN_ROLE = "pepper_admin"; - private static final String USER_ADMIN_ROLE = "study_admin"; + private static final String SQL_SELECT_STUDY_GROUP = + "SELECT dg.group_id FROM ddp_group dg WHERE dg.name = ?"; + + private static final String SQL_SELECT_STUDY_ROLES = + "SELECT ar.role_id, ar.name FROM access_role ar " + + "JOIN ddp_group_role dgr on ar.role_id = dgr.role_id " + + "WHERE dgr.group_id = ?"; + + private static final String SQL_SELECT_STUDY_ROLES_FOR_ADMIN = + "SELECT ar.role_id, ar.name FROM access_role ar " + + "JOIN ddp_group_role dgr on ar.role_id = dgr.role_id " + + "WHERE dgr.group_id = ? " + + "AND dgr.admin_role_id = (select role_id from access_role ar where ar.name = ?)"; private static final String SQL_SELECT_ROLES_FOR_USER = "SELECT ar.role_id, ar.name FROM access_role ar " + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + "WHERE aurg.user_id = ? AND aurg.group_id = ?"; - private static final String SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID = + private static final String SQL_SELECT_ROLES_FOR_STUDY_USERS = + "SELECT aurg.user_id, ar.name FROM access_role ar " + + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + + "WHERE aurg.group_id = ?"; + + private static final String SQL_SELECT_ROLES_FOR_GROUP_AND_USER = "SELECT ar.role_id, ar.name, dg.group_id FROM access_role ar " + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + "JOIN ddp_group dg on dg.group_id = aurg.group_id " - + "WHERE aurg.user_id = ? AND dg.name = ?"; + + "WHERE aurg.user_id = ? AND dg.group_id = ?"; private static final String SQL_SELECT_USER_ROLE = "SELECT aurg.user_role_group_id FROM access_user_role_group aurg " @@ -49,12 +73,18 @@ public class UserAdminService { private static final String SQL_SELECT_ROLE = "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; - private static final String SQL_SELECT_GROUP = - "SELECT dg.group_id FROM ddp_group dg WHERE dg.name = ?"; + private static final String SQL_SELECT_ROLE_FOR_ADMIN = + "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = "SELECT au.user_id, au.name FROM access_user au WHERE au.email = ?"; + private static final String SQL_SELECT_USERS_FOR_GROUP = + "SELECT au.user_id, au.name, au.email, au.phone_number FROM access_user au " + + "JOIN access_user_role_group aurg on aurg.user_id = au.user_id " + + "JOIN ddp_group dg on dg.group_id = aurg.group_id " + + "WHERE dg.group_id = ? AND au.is_active = 1"; + private static final String SQL_INSERT_USER_ROLE = "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; @@ -73,8 +103,8 @@ public class UserAdminService { private static final String SQL_DELETE_GROUP = "DELETE FROM ddp_group WHERE group_id = ?"; - private static final String SQL_INSERT_USER = - "INSERT INTO ddp_group SET name = ?"; + private static final String SQL_INSERT_GROUP_ROLE = + "INSERT INTO ddp_group_role SET group_id = ?, role_id = ?, admin_role_id = ?"; public static final String SQL_SELECT_GROUP_FOR_REALM = "SELECT dg.group_id, dg.name from ddp_group dg " @@ -87,36 +117,34 @@ public UserAdminService(String operatorId, String studyGroup) { this.studyGroup = studyGroup; } - public void getStudyRoles() { - - int groupId = verifyOperatorAndGroup(studyGroup); - throw new DsmInternalError("Not implemented"); - } - public void addUserToRoles(UserRoleRequest req) { validateRoleRequest(req, true); - int groupId = verifyOperatorAndGroup(studyGroup); + int groupId = verifyStudyGroup(studyGroup); + Map studyRoles = getAdminRoles(groupId); + List roleNames = req.getRoles(); + validateRoles(roleNames, studyRoles.keySet()); for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - addUserRoles(userEmail, req.getRoles(), groupId, studyGroup); + addUserRoles(userEmail, roleNames, groupId, studyRoles); } } - protected void addUserRoles(String email, List roles, int groupId, String studyGroup) { + protected void addUserRoles(String email, List roles, int groupId, Map studyRoles) { int userId = getUserByEmail(email, groupId); for (String role : roles) { if (StringUtils.isBlank(role)) { throw new DsmInternalError("Invalid role: blank"); } - int roleId = verifyRole(role, groupId); + // role names already validated + int roleId = studyRoles.get(role).roleId; try { addUserRole(userId, roleId, groupId); - String msg = String.format("Set up role %s for user %s in study group %s", role, email, studyGroup); + String msg = String.format("Added role %s for user %s in study group %s", role, email, studyGroup); log.info(msg); } catch (Exception e) { String msg = String.format("Error adding user %s to role %s for study group %s", email, role, studyGroup); @@ -128,17 +156,20 @@ protected void addUserRoles(String email, List roles, int groupId, Strin public void removeUserFromRoles(UserRoleRequest req) { validateRoleRequest(req, true); - int groupId = verifyOperatorAndGroup(studyGroup); + int groupId = verifyStudyGroup(studyGroup); + Map studyRoles = getAdminRoles(groupId); + List roleNames = req.getRoles(); + validateRoles(roleNames, studyRoles.keySet()); for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - removeUserRoles(userEmail, req.getRoles(), groupId, studyGroup); + removeUserRoles(userEmail, roleNames, groupId, studyRoles); } } - protected void removeUserRoles(String email, List roles, int groupId, String studyGroup) { + protected void removeUserRoles(String email, List roles, int groupId, Map studyRoles) { // TODO loop is similar to addUserRoles -DC int userId = getUserByEmail(email, groupId); @@ -146,7 +177,8 @@ protected void removeUserRoles(String email, List roles, int groupId, St if (StringUtils.isBlank(role)) { throw new DsmInternalError("Invalid role: blank"); } - int roleId = verifyRole(role, groupId); + // role names already validated + int roleId = studyRoles.get(role).roleId; try { // ignore failure to remove deleteUserRole(userId, roleId, groupId); @@ -159,21 +191,153 @@ protected void removeUserRoles(String email, List roles, int groupId, St } } - public UserRoleResponse getUserRoles(UserRoleRequest req) { - validateRoleRequest(req, false); + protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { + if (CollectionUtils.isEmpty(req.getUsers())) { + throw new DSMBadRequestException("Invalid users: empty"); + } + if (CollectionUtils.isEmpty(req.getRoles())) { + if (includesRoles) { + throw new DSMBadRequestException("Invalid roles: empty"); + } + } else if (!includesRoles) { + throw new DSMBadRequestException("Invalid roles: should be empty"); + } + } - int groupId = verifyOperatorAndGroup(studyGroup); + protected void validateRoles(List roleNames, Set validRoleNames) { + if (validRoleNames.containsAll(roleNames)) { + return; + } + Collection badRoles = CollectionUtils.subtract(validRoleNames, roleNames); + String msg = String.format("Invalid roles for study group %s: %s", studyGroup, String.join(",", badRoles)); + throw new DSMBadRequestException(msg); + } - UserRoleResponse userRoleResponse = new UserRoleResponse(); - for (String userEmail: req.getUsers()) { - if (StringUtils.isBlank(userEmail)) { - throw new DSMBadRequestException("Invalid user email: blank"); + public void createUser(AddUserRequest req) { + if (StringUtils.isBlank(req.getName())) { + throw new DSMBadRequestException("Invalid user name: blank"); + } + if (StringUtils.isBlank(req.getEmail())) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + // TODO: Currently we do not assign users to study groups, but when we do validate UserRequest.studyGroup -DC + + UserDao userDao = new UserDao(); + userDao.create(req.asUserDto()); + } + + public void removeUser(AddUserRequest req) { + if (StringUtils.isBlank(req.getEmail())) { + throw new DSMBadRequestException("Invalid user email: blank"); + } + + int userId = getUserByEmail(req.getEmail(), -1); + deleteUserRoles(userId); + UserDao userDao = new UserDao(); + userDao.delete(userId); + } + + public UserRoleResponse getUserRoles(UserRequest req) { + int groupId = verifyStudyGroup(studyGroup); + Map studyRoles = getAdminRoles(groupId); + + // get access_user info for the users + Map studyUsers = getStudyUsers(groupId, req); + // if list of users provided, create a list of just those users + // get roles for users (should include admin predictate) + Map> rolesByUser = getRolesForStudyUsers(groupId); + + UserRoleResponse res = new UserRoleResponse(); + for (var entry: studyUsers.entrySet()) { + UserInfo userInfo = entry.getValue(); + Set roles = rolesByUser.get(entry.getKey()); + List userRoles = createUserRoles(roles, studyRoles); + userInfo.addRoles(userRoles); + res.addUser(userInfo); + } + + return res; + } + + protected static Map getStudyUsers(int groupId, UserRequest req) { + Map allStudyUsers = getUsersForGroup(groupId); + if (req == null || CollectionUtils.isEmpty(req.getUsers())) { + return allStudyUsers; + } + + Map emailToId = allStudyUsers.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().email, + Map.Entry::getKey)); + + Map users = new HashMap<>(); + for (String email: req.getUsers()) { + Integer id = emailToId.get(email); + if (id == null) { + throw new DSMBadRequestException("Invalid user email " + email); + } + users.put(id, allStudyUsers.get(id)); + } + return users; + } + + protected static List createUserRoles(Set roles, Map studyRoles) { + List userRoles = new ArrayList<>(); + + for (var entry: studyRoles.entrySet()) { + RoleInfo roleInfo = entry.getValue(); + userRoles.add(new UserRole(roleInfo.name, roleInfo.displayText, roles.contains(entry.getKey()))); + } + return userRoles; + } + + public void addStudyRole(StudyRoleRequest req) { + int groupId = verifyStudyGroup(studyGroup); + + Map adminRoles = getAdminRoles(groupId); + if (!adminRoles.containsKey(PEPPER_ADMIN_ROLE)) { + throw new DSMBadRequestException("Operator does not have add study role privileges for study group " + + studyGroup); + } + + List roles = req.getRoles(); + if (CollectionUtils.isEmpty(roles)) { + throw new DSMBadRequestException("No roles provided"); + } + + for (StudyRoleRequest.RoleInfo roleInfo: roles) { + String role = roleInfo.roleName; + if (StringUtils.isBlank(role)) { + throw new DSMBadRequestException("Invalid role name: blank"); + } + int roleId = getRoleId(role); + if (roleId == -1) { + throw new DSMBadRequestException("Invalid role name: " + role); + } + + int adminRoleId = -1; + String adminRole = roleInfo.adminRoleName; + if (!StringUtils.isBlank(adminRole)) { + adminRoleId = getRoleId(adminRole); + if (adminRoleId == -1) { + throw new DSMBadRequestException("Invalid admin role name: " + adminRole); + } } - int userId = getUserByEmail(userEmail, groupId); + addGroupRole(groupId, roleId, adminRoleId); + } + } - userRoleResponse.addUserRoles(userEmail, getRolesForUser(userId, groupId)); + protected Map getAdminRoles(int studyGroupId) { + int adminId; + try { + adminId = Integer.parseInt(operatorId); + } catch (NumberFormatException e) { + throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); + } + Map adminRoles = verifyOperatorGroup(adminId, studyGroupId); + if (adminRoles == null || adminRoles.isEmpty()) { + throw new DSMBadRequestException("Operator does not have user administrator privileges for study group " + + studyGroup); } - return userRoleResponse; + return adminRoles; } public static String getStudyGroup(Map queryParams) { @@ -230,100 +394,46 @@ private static NameAndId _getStudyGroupForRealm(String realm) { return (NameAndId) res.resultValue; } - protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { + protected static int verifyStudyGroup(String studyGroup) { if (StringUtils.isBlank(studyGroup)) { throw new DSMBadRequestException("Invalid study group: blank"); } - if (CollectionUtils.isEmpty(req.getUsers())) { - throw new DSMBadRequestException("Invalid users: empty"); - } - if (CollectionUtils.isEmpty(req.getRoles())) { - if (includesRoles) { - throw new DSMBadRequestException("Invalid roles: empty"); + return inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_STUDY_GROUP)) { + stmt.setString(1, studyGroup); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1); + } else { + throw new DSMBadRequestException("Invalid study group " + studyGroup); + } + } + } catch (SQLException e) { + throw new DsmInternalError("Error getting study group", e); } - } else if (!includesRoles) { - throw new DSMBadRequestException("Invalid roles: should be empty"); - } - } - - public void createUser(UserRequest req) { - if (StringUtils.isBlank(req.getName())) { - throw new DSMBadRequestException("Invalid user name: blank"); - } - if (StringUtils.isBlank(req.getEmail())) { - throw new DSMBadRequestException("Invalid user email: blank"); - } - // TODO: Currently we do not assign users to study groups, but when we do validate UserRequest.studyGroup -DC - - UserDao userDao = new UserDao(); - userDao.create(req.asUserDto()); - } - - public void removeUser(UserRequest req) { - if (StringUtils.isBlank(req.getEmail())) { - throw new DSMBadRequestException("Invalid user email: blank"); - } - - int userId = getUserByEmail(req.getEmail(), -1); - deleteUserRoles(userId); - UserDao userDao = new UserDao(); - userDao.delete(userId); - } - - protected int verifyOperatorAndGroup(String studyGroup) { - int adminId; - try { - adminId = Integer.parseInt(operatorId); - } catch (NumberFormatException e) { - throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); - } - return verifyOperatorForGroup(adminId, studyGroup); + }); } - public void addStudyRole(StudyRoleRequest req) { - String group = studyGroup; - if (StringUtils.isBlank(group)) { - throw new DSMBadRequestException("Invalid study group: blank"); - } - String role = req.getRole(); - if (StringUtils.isBlank(role)) { - throw new DSMBadRequestException("Invalid role: blank"); - } - - int adminId; - try { - adminId = Integer.parseInt(operatorId); - } catch (NumberFormatException e) { - throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); + protected static Map verifyOperatorGroup(int operatorId, int groupId) { + Map adminRoles = null; + List roles = getRolesForUser(operatorId, groupId); + if (roles.isEmpty()) { + log.info("No roles found for operator {} and group {}", operatorId, groupId); + return adminRoles; } - int groupId = verifyOperatorForGroup(adminId, group); - try { - addRole(role, groupId); - } catch (Exception e) { - String msg = String.format("Error adding role %s to study group %s", role, group); - throw new DsmInternalError(msg, e); + if (roles.contains(PEPPER_ADMIN_ROLE)) { + adminRoles = getAllRolesForStudy(groupId); + } else if (roles.contains(USER_ADMIN_ROLE)) { + adminRoles = getRolesForAdmin(groupId, USER_ADMIN_ROLE); } - } - protected static int verifyOperatorForGroup(int operatorId, String studyGroup) { - - List roles = getRolesAndGroupForUser(operatorId, studyGroup); - if (roles.isEmpty()) { - String msg = String.format("No roles found for operator %s and group %s", operatorId, studyGroup); - throw new DSMBadRequestException(msg); - } - List roleNames = roles.stream().map(r -> r.name).collect(Collectors.toList()); - if (!roleNames.contains(USER_ADMIN_ROLE)) { - throw new DSMBadRequestException("Operator does not have administrator privileges: " + operatorId); - } - return roles.get(0).id; + return adminRoles; } protected static List getRolesForUser(int userId, int groupId) { - SimpleResult res = inTransaction(conn -> { + return inTransaction(conn -> { List roles = new ArrayList<>(); - SimpleResult dbVals = new SimpleResult(roles); try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_USER)) { stmt.setInt(1, userId); stmt.setInt(2, groupId); @@ -333,95 +443,103 @@ protected static List getRolesForUser(int userId, int groupId) { } } } catch (SQLException e) { - dbVals.resultException = e; + throw new DsmInternalError("Error getting roles groups for user", e); } - return dbVals; + return roles; }); - - if (res.resultException != null) { - throw new DsmInternalError("Error getting roles groups for user", res.resultException); - } - return (List) res.resultValue; } - protected static List getRolesAndGroupForUser(int userId, String studyGroup) { - SimpleResult res = inTransaction(conn -> { - List roles = new ArrayList<>(); - SimpleResult dbVals = new SimpleResult(roles); - try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_GROUP_AND_USER_ID)) { - stmt.setInt(1, userId); - stmt.setString(2, studyGroup); + protected static Map getAllRolesForStudy(int groupId) { + return inTransaction(conn -> { + Map roles = new HashMap<>(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_STUDY_ROLES)) { + stmt.setInt(1, groupId); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { - roles.add(new NameAndId(rs.getString(2), rs.getInt(3))); + // TODO: temp until we have display text + String name = rs.getString(2); + RoleInfo info = new RoleInfo(rs.getInt(1), name, name); + roles.put(name, info); } } } catch (SQLException e) { - dbVals.resultException = e; + throw new DsmInternalError("Error getting roles for study group", e); } - return dbVals; + return roles; }); - - if (res.resultException != null) { - throw new DsmInternalError("Error getting study groups for user", res.resultException); - } - return (List) res.resultValue; } - protected static int verifyRole(String role, int groupId) { - // TODO: Currently we do not track the valid roles for a group, but verify with - // groupId once we do -DC - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLE)) { - stmt.setString(1, role); + + protected static Map getRolesForAdmin(int groupId, String adminRole) { + return inTransaction(conn -> { + Map roles = new HashMap<>(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_STUDY_ROLES_FOR_ADMIN)) { + stmt.setInt(1, groupId); + stmt.setString(2, adminRole); try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - dbVals.resultValue = rs.getInt(1); + while (rs.next()) { + // TODO: temp until we have display text + String name = rs.getString(2); + RoleInfo info = new RoleInfo(rs.getInt(1), name, name); + roles.put(name, info); } } } catch (SQLException e) { - dbVals.resultException = e; + throw new DsmInternalError("Error getting study group roles for admin", e); } - return dbVals; + return roles; }); + } - if (res.resultException != null) { - String msg = String.format("Error getting role %s for study group, ID: %d", role, groupId); - throw new DsmInternalError(msg, res.resultException); - } - if (res.resultValue == null) { - throw new DSMBadRequestException("Invalid role for study group: " + role); + protected int verifyRoleForAdmin(String role, Map adminRoles) { + int roleId = getRoleId(role); + if (roleId == -1 || !adminRoles.containsKey(role)) { + String msg = String.format("Invalid role %s for study group %s", role, studyGroup); + if (roleId != -1) { + log.info("{}: role is valid but admin does not have permission", msg); + } + throw new DSMBadRequestException(msg); } - return (int) res.resultValue; + return roleId; } - protected static int verifyStudyGroup(String studyGroup) { - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP)) { - stmt.setString(1, studyGroup); + protected static Map> getRolesForStudyUsers(int groupId) { + return inTransaction(conn -> { + Map> roles = new HashMap<>(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLES_FOR_STUDY_USERS)) { + stmt.setInt(1, groupId); try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - dbVals.resultValue = rs.getInt(1); + while (rs.next()) { + int userId = rs.getInt(1); + String role = rs.getString(2); + Set userRoles = roles.computeIfAbsent(userId, k -> new HashSet<>()); + userRoles.add(role); } } } catch (SQLException e) { - dbVals.resultException = e; + throw new DsmInternalError("Error getting user roles for study group " + groupId, e); } - return dbVals; + return roles; }); + } - if (res.resultException != null) { - String msg = String.format("Error getting study group %s", studyGroup); - throw new DsmInternalError(msg, res.resultException); - } - - if (res.resultValue == null) { - throw new DSMBadRequestException("Invalid study group: " + studyGroup); - } - return (int) res.resultValue; + protected static int getRoleId(String role) { + return inTransaction(conn -> { + int id = -1; + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_ROLE)) { + stmt.setString(1, role); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + id = rs.getInt(1); + } + } + } catch (SQLException e) { + String msg = String.format("Error getting role %s", role); + throw new DsmInternalError(msg, e); + } + return id; + }); } protected static int getUserByEmail(String email, int groupId) { @@ -484,6 +602,25 @@ protected static int getUserRole(int userId, int roleId, int groupId) { return (int) res.resultValue; } + protected static Map getUsersForGroup(int groupId) { + return inTransaction(conn -> { + Map res = new HashMap<>(); + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USERS_FOR_GROUP)) { + stmt.setInt(1, groupId); + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + int roleId = rs.getInt(1); + UserInfo info = new UserInfo(rs.getString(2), rs.getString(3), rs.getString(4)); + res.put(roleId, info); + } + } + } catch (SQLException e) { + throw new DsmInternalError("Error getting user info for group " + groupId, e); + } + return res; + }); + } + protected static int addUserRole(int userId, int roleId, int groupId) throws Exception { // since we do not have a key constraint for duplicate entries we need to check first int userRoleId = getUserRole(userId, roleId, groupId); @@ -517,32 +654,37 @@ protected static int addUserRole(int userId, int roleId, int groupId) throws Exc return (int) res.resultValue; } - protected static int addRole(String role, int groupId) throws Exception { - // TODO: Currently we do not track the valid roles for a group, but enroll with - // groupId once we do -DC - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_ROLE, Statement.RETURN_GENERATED_KEYS)) { - stmt.setString(1, role); + protected static int addGroupRole(int groupId, int roleId, int adminRoleId) { + String errMsg = "Error adding group role: groupId=%d, roleId=%d, adminRoleId=%d. "; + int res = inTransaction(conn -> { + int id = -1; + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_GROUP_ROLE, Statement.RETURN_GENERATED_KEYS)) { + stmt.setInt(1, groupId); + stmt.setInt(2, roleId); + if (adminRoleId == -1) { + stmt.setNull(3, Types.INTEGER); + } else { + stmt.setInt(3, adminRoleId); + } int result = stmt.executeUpdate(); if (result != 1) { - dbVals.resultException = new DsmInternalError("Result count was " + result); + throw new DsmInternalError(errMsg + "Result count for addGroupRole was " + result); } try (ResultSet rs = stmt.getGeneratedKeys()) { if (rs.next()) { - dbVals.resultValue = rs.getInt(1); + id = rs.getInt(1); } } } catch (SQLException ex) { - dbVals.resultException = ex; + throw new DsmInternalError(errMsg, ex); } - return dbVals; + return id; }); - if (res.resultException != null) { - throw new DsmInternalError("Error adding role " + role, res.resultException); + if (res == -1) { + throw new DsmInternalError(errMsg); } - return (int) res.resultValue; + return res; } protected static int addStudyGroup(String groupName) { @@ -552,7 +694,7 @@ protected static int addStudyGroup(String groupName) { stmt.setString(1, groupName); int result = stmt.executeUpdate(); if (result != 1) { - dbVals.resultException = new DsmInternalError("Result count was " + result); + dbVals.resultException = new DsmInternalError("Result count for addStudyGroup was " + result); } try (ResultSet rs = stmt.getGeneratedKeys()) { if (rs.next()) { @@ -610,6 +752,19 @@ protected static int deleteStudyGroup(int groupId) { }); } + + protected static class RoleInfo { + public final int roleId; + public final String name; + public final String displayText; + + public RoleInfo(int roleId, String name, String displayText) { + this.roleId = roleId; + this.name = name; + this.displayText = displayText; + } + } + protected static class NameAndId { public final String name; public final int id; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java new file mode 100644 index 0000000000..9ab94e3cde --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java @@ -0,0 +1,29 @@ +package org.broadinstitute.dsm.service.admin; + +import java.util.ArrayList; +import java.util.List; + +public class UserInfo { + public final String email; + public final String name; + public final String phone; + public List roles; + + public UserInfo(String email, String name, String phone, List roles) { + this.email = email; + this.name = name; + this.phone = phone; + this.roles = roles; + } + + public UserInfo(String email, String name, String phone) { + this.email = email; + this.name = name; + this.phone = phone; + this.roles = new ArrayList<>(); + } + + public void addRoles(List roles) { + this.roles = roles; + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java index c9ef228f65..14bad1b135 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRequest.java @@ -1,24 +1,14 @@ package org.broadinstitute.dsm.service.admin; +import java.util.List; + import lombok.Data; -import org.broadinstitute.dsm.db.dto.user.UserDto; @Data public class UserRequest { + private final List users; - private final String name; - private final String email; - private final String phone; - - // TODO extend to include roles for user -DC - - public UserRequest(String name, String email, String phone) { - this.name = name; - this.email = email; - this.phone = phone; - } - - public UserDto asUserDto() { - return new UserDto(-1, name, email, phone); + public UserRequest(List users) { + this.users = users; } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java new file mode 100644 index 0000000000..c3a770bedb --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java @@ -0,0 +1,13 @@ +package org.broadinstitute.dsm.service.admin; + +public class UserRole { + public final String name; + public final String displayText; + public final boolean hasRole; + + public UserRole(String name, String displayText, boolean hasRole) { + this.name = name; + this.displayText = displayText; + this.hasRole = hasRole; + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java index d897391d01..cbe053502f 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRoleResponse.java @@ -3,29 +3,17 @@ import java.util.ArrayList; import java.util.List; -public class UserRoleResponse { +import lombok.Data; - private List userRolesList; +@Data +public class UserRoleResponse { + private List users; public UserRoleResponse() { - this.userRolesList = new ArrayList<>(); - } - - public void addUserRoles(String userEmail, List roles) { - this.userRolesList.add(new UserRoles(userEmail, roles)); + this.users = new ArrayList<>(); } - public List getUserRoles() { - return userRolesList; - } - - public static class UserRoles { - public final String user; - public final List roles; - - public UserRoles(String user, List roles) { - this.user = user; - this.roles = roles; - } + public void addUser(UserInfo userInfo) { + this.users.add(userInfo); } } diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 74bcff7d24..848def620a 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -2,6 +2,7 @@ import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; import static org.broadinstitute.ddp.db.TransactionWrapper.inTransaction; +import static org.broadinstitute.dsm.service.admin.UserAdminService.USER_ADMIN_ROLE; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -107,19 +108,19 @@ private void removeRoleForUser(int roleId, int userId) { } @Test - public void testVerifyRole() { - int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + public void testgetRoleId() { + int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); } @Test - public void testVerifyOperatorForGroup() { - int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + public void testVerifyOperatorGroup() { + int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); int userId = createTestUser("test_admin1@study.org", roleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); + UserAdminService.verifyOperatorGroup(userId, groupId); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("No roles found")); @@ -130,21 +131,29 @@ public void testVerifyOperatorForGroup() { } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } + + try { + Map adminRoles = UserAdminService.verifyOperatorGroup(userId, groupId); + Assert.assertTrue(adminRoles.isEmpty()); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + try { - UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); + Map adminRoles = UserAdminService.verifyOperatorGroup(userId, groupId); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("does not have administrator privileges")); } - int adminRoleId = UserAdminService.verifyRole("study_admin", -1); + int adminRoleId = UserAdminService.getRoleId(USER_ADMIN_ROLE); try { addUserRole(userId, adminRoleId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } try { - UserAdminService.verifyOperatorForGroup(userId, TEST_GROUP); + UserAdminService.verifyOperatorGroup(userId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } @@ -152,7 +161,7 @@ public void testVerifyOperatorForGroup() { @Test public void testGetUserByEmailAndGroup() { - int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); String email = "testUser@study.org"; int userId = createTestUser(email, -1); @@ -168,16 +177,17 @@ public void testGetUserByEmailAndGroup() { @Test public void testAddUserToRoles() { int operatorId = createAdminUser("test_admin2@study.org"); + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - UserAdminService.verifyOperatorForGroup(operatorId, TEST_GROUP); + UserAdminService.verifyOperatorGroup(operatorId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } String role1 = "upload_onc_history"; - int roleId1 = UserAdminService.verifyRole(role1, -1); + int roleId1 = UserAdminService.getRoleId(role1); Assert.assertTrue(roleId1 > 0); String role2 = "upload_ror_file"; - int roleId2 = UserAdminService.verifyRole(role2, -1); + int roleId2 = UserAdminService.getRoleId(role2); Assert.assertTrue(roleId2 > 0); List roles = List.of(role1, role2); @@ -190,7 +200,6 @@ public void testAddUserToRoles() { List users = List.of(user1, user2); - int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); UserRoleRequest req = new UserRoleRequest(users, roles); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); @@ -264,15 +273,15 @@ public void testAddUserToRoles() { @Test public void testAddAndRemoveUser() { int operatorId = createAdminUser("test_admin3@study.org"); - int groupId = -1; + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - groupId = UserAdminService.verifyOperatorForGroup(operatorId, TEST_GROUP); + UserAdminService.verifyOperatorGroup(operatorId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } String email = "testUser4@study.org"; - UserRequest req = new UserRequest("testUser4", email, null); + AddUserRequest req = new AddUserRequest("testUser4", email, null, null); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { @@ -288,7 +297,7 @@ public void testAddAndRemoveUser() { Assert.fail("Exception verifying UserAdminService.createUser: " + getStackTrace(e)); } - int roleId = UserAdminService.verifyRole("upload_onc_history", -1); + int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); try { @@ -339,7 +348,7 @@ public void testGetStudyGroup() { } private int createAdminUser(String email) { - int adminRoleId = UserAdminService.verifyRole("study_admin", -1); + int adminRoleId = UserAdminService.getRoleId(USER_ADMIN_ROLE); int userId = createTestUser(email, adminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { @@ -350,21 +359,31 @@ private int createAdminUser(String email) { } return userId; } - - private void setUserRoles(String email, List roles, String studyGroup) throws Exception { + + private void addAdminRoles(int adminUserId, int groupId, List roles) { + try { + for (String role : roles) { + int roleId = UserAdminService.getRoleId(role); + UserAdminService.addUserRole(adminUserId, roleId, groupId); + } + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + } + } + + private void setUserRoles(String email, List roles, String studyGroup) { try { int userId = UserAdminService.getUserByEmail(email, -1); int groupId = UserAdminService.verifyStudyGroup(studyGroup); for (String role : roles) { - int roleId = UserAdminService.verifyRole(role, -1); + int roleId = UserAdminService.getRoleId(role); UserAdminService.addUserRole(userId, roleId, groupId); String msg = String.format("Set up role %s for user %s in study group %s", role, email, studyGroup); log.info(msg); } } catch (Exception e) { - log.error("Exception in setUserRoles: " + getStackTrace(e)); - throw e; + Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } } From 40347c7a30befdb804d4e4d1e6dd08803aca8920 Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 14 Jul 2023 15:06:25 -0400 Subject: [PATCH 22/35] WIP --- .../dsm/service/admin/UserAdminService.java | 20 ++++- .../dsm/service/admin/UserInfo.java | 11 ++- .../dsm/service/admin/UserRole.java | 9 +- .../service/admin/UserAdminServiceTest.java | 89 +++++++++++++++---- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 3c95f2073c..7ee23ad898 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -91,6 +91,9 @@ public class UserAdminService { private static final String SQL_DELETE_USER_ROLES = "DELETE FROM access_user_role_group WHERE user_id = ?"; + private static final String SQL_DELETE_GROUP_ROLE = + "DELETE FROM ddp_group_role WHERE group_role_id = ?"; + private static final String SQL_DELETE_USER_ROLE = "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ? AND group_id = ?"; @@ -265,7 +268,7 @@ protected static Map getStudyUsers(int groupId, UserRequest r return allStudyUsers; } - Map emailToId = allStudyUsers.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().email, + Map emailToId = allStudyUsers.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getEmail(), Map.Entry::getKey)); Map users = new HashMap<>(); @@ -491,6 +494,9 @@ protected static Map getRolesForAdmin(int groupId, String admi }); } + protected static void setAdminForRole(int roleId, int adminRoleId, int groupId) { + + } protected int verifyRoleForAdmin(String role, Map adminRoles) { int roleId = getRoleId(role); @@ -740,6 +746,18 @@ protected static int deleteUserRoles(int userId) { }); } + protected static int deleteGroupRole(int groupRoleId) { + return inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_GROUP_ROLE)) { + stmt.setInt(1, groupRoleId); + return stmt.executeUpdate(); + } catch (SQLException ex) { + String msg = String.format("Error deleting group role: groupRoleId=%d", groupRoleId); + throw new DsmInternalError(msg, ex); + } + }); + } + protected static int deleteStudyGroup(int groupId) { return inTransaction(conn -> { try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_GROUP)) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java index 9ab94e3cde..9aa11c64f7 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java @@ -3,11 +3,14 @@ import java.util.ArrayList; import java.util.List; +import lombok.Data; + +@Data public class UserInfo { - public final String email; - public final String name; - public final String phone; - public List roles; + private final String email; + private final String name; + private final String phone; + private List roles; public UserInfo(String email, String name, String phone, List roles) { this.email = email; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java index c3a770bedb..05436562ea 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserRole.java @@ -1,9 +1,12 @@ package org.broadinstitute.dsm.service.admin; +import lombok.Data; + +@Data public class UserRole { - public final String name; - public final String displayText; - public final boolean hasRole; + private final String name; + private final String displayText; + private final boolean hasRole; public UserRole(String name, String displayText, boolean hasRole) { this.name = name; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 848def620a..532946ac1f 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -2,6 +2,7 @@ import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; import static org.broadinstitute.ddp.db.TransactionWrapper.inTransaction; +import static org.broadinstitute.dsm.service.admin.UserAdminService.PEPPER_ADMIN_ROLE; import static org.broadinstitute.dsm.service.admin.UserAdminService.USER_ADMIN_ROLE; import java.sql.PreparedStatement; @@ -12,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.broadinstitute.dsm.DbTxnBaseTest; @@ -29,10 +31,13 @@ public class UserAdminServiceTest extends DbTxnBaseTest { private static final Map> createdUserRoles = new HashMap<>(); + private static List createdGroupRoles = new ArrayList<>(); private static final String TEST_GROUP = "test_group"; private static final String TEST_INSTANCE = "test_instance"; private static int studyGroupId; private static int ddpInstanceId; + private static int pepperAdminRoleId; + private static int userAdminRoleId; private static final String SQL_INSERT_DDP_INSTANCE = "INSERT INTO ddp_instance SET instance_name = ?, is_active = 1, auth0_token = 1, migrated_ddp = 0"; @@ -50,10 +55,18 @@ public class UserAdminServiceTest extends DbTxnBaseTest { public static void setup() { studyGroupId = UserAdminService.addStudyGroup(TEST_GROUP); ddpInstanceId = createTestInstance(TEST_INSTANCE, studyGroupId); + pepperAdminRoleId = UserAdminService.getRoleId(PEPPER_ADMIN_ROLE); + Assert.assertTrue(pepperAdminRoleId != -1); + userAdminRoleId = UserAdminService.getRoleId(USER_ADMIN_ROLE); + Assert.assertTrue(userAdminRoleId != -1); } @AfterClass public static void tearDown() { + for (int groupRoleId: createdGroupRoles) { + UserAdminService.deleteGroupRole(groupRoleId); + } + UserDao userDao = new UserDao(); for (var entry: createdUserRoles.entrySet()) { int userId = entry.getKey(); @@ -107,6 +120,17 @@ private void removeRoleForUser(int roleId, int userId) { createdUserRoles.put(userId, newRoleIds); } + private int addGroupRole(int roleId, int adminRoleId) { + int groupRoleId = UserAdminService.addGroupRole(studyGroupId, roleId, adminRoleId); + createdGroupRoles.add(groupRoleId); + return groupRoleId; + } + + private void removeGroupRole(int groupRoleId) { + UserAdminService.deleteGroupRole(groupRoleId); + createdGroupRoles.remove(groupRoleId); + } + @Test public void testgetRoleId() { int roleId = UserAdminService.getRoleId("upload_onc_history"); @@ -175,8 +199,8 @@ public void testGetUserByEmailAndGroup() { } @Test - public void testAddUserToRoles() { - int operatorId = createAdminUser("test_admin2@study.org"); + public void testUserRoles() { + int operatorId = createAdminUser("test_admin2@study.org", userAdminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { UserAdminService.verifyOperatorGroup(operatorId, groupId); @@ -203,6 +227,15 @@ public void testAddUserToRoles() { UserRoleRequest req = new UserRoleRequest(users, roles); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); + try { + service.addUserToRoles(req); + Assert.fail("UserAdminService.addUserToRoles should fail with roles not in study"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid user")); + } + + int groupRoleId1 = addGroupRole(roleId1, userAdminRoleId); + int groupRoleId2 = addGroupRole(roleId2, userAdminRoleId); try { service.addUserToRoles(req); } catch (Exception e) { @@ -225,7 +258,7 @@ public void testAddUserToRoles() { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } - UserRoleRequest getReq = new UserRoleRequest(users, null); + UserRequest getReq = new UserRequest(users); UserRoleResponse res = null; try { res = service.getUserRoles(getReq); @@ -233,12 +266,15 @@ public void testAddUserToRoles() { Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); } - List userRoles = res.getUserRoles(); - for (UserRoleResponse.UserRoles resRoles: userRoles) { - Assert.assertTrue("RemoveUserFromRole removed wrong role", resRoles.roles.contains(role2)); - Assert.assertFalse("RemoveUserFromRole did not remove role", resRoles.roles.contains(role1)); + List userInfoList = res.getUsers(); + Map> userRoles = getUserRoles(userInfoList); + for (var resRoles: userRoles.entrySet()) { + Assert.assertTrue("RemoveUserFromRole removed wrong role", resRoles.getValue().contains(role2)); + Assert.assertFalse("RemoveUserFromRole did not remove role", resRoles.getValue().contains(role1)); } + // TODO: assert that result also has unassigned roles + // adjust cleanup removeRoleForUser(roleId1, userId1); removeRoleForUser(roleId1, userId2); @@ -250,19 +286,30 @@ public void testAddUserToRoles() { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } - UserRoleRequest getReq2 = new UserRoleRequest(users, null); + UserRequest getReq2 = new UserRequest(users); try { res = service.getUserRoles(getReq2); } catch (Exception e) { Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); } - userRoles = res.getUserRoles(); - for (UserRoleResponse.UserRoles resRoles: userRoles) { - if (resRoles.user.equals(user1)) { - Assert.assertTrue(resRoles.roles.contains(role2)); + userInfoList = res.getUsers(); + for (var userInfo: userInfoList) { + List resRoles = userInfo.getRoles(); + if (userInfo.getEmail().equals(user1)) { + Assert.assertEquals(2, resRoles.size()); + + List hasRole = resRoles.stream().filter(UserRole::isHasRole).collect(Collectors.toList()); + Assert.assertEquals(1, hasRole.size()); + + List filteredRoles = resRoles.stream().filter(r -> r.getName().equals(role2)).collect(Collectors.toList()); + Assert.assertEquals(1, filteredRoles.size()); + Assert.assertTrue(filteredRoles.get(0).isHasRole()); } else { - Assert.assertTrue("RemoveUserFromRole did not remove role", resRoles.roles.isEmpty()); + Assert.assertEquals(2, resRoles.size()); + + List hasRole = resRoles.stream().filter(UserRole::isHasRole).collect(Collectors.toList()); + Assert.assertTrue("RemoveUserFromRole did not remove role", hasRole.isEmpty()); } } @@ -272,7 +319,7 @@ public void testAddUserToRoles() { @Test public void testAddAndRemoveUser() { - int operatorId = createAdminUser("test_admin3@study.org"); + int operatorId = createAdminUser("test_admin3@study.org", userAdminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { UserAdminService.verifyOperatorGroup(operatorId, groupId); @@ -347,11 +394,11 @@ public void testGetStudyGroup() { } } - private int createAdminUser(String email) { - int adminRoleId = UserAdminService.getRoleId(USER_ADMIN_ROLE); + private int createAdminUser(String email, int adminRoleId) { int userId = createTestUser(email, adminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { + log.info("TEMP: createAdminUser: userId={}, adminRoleId={}", userId, adminRoleId); addUserRole(userId, adminRoleId, groupId); log.info("Created admin user wih id {}", userId); } catch (Exception e) { @@ -387,6 +434,16 @@ private void setUserRoles(String email, List roles, String studyGroup) { } } + private Map> getUserRoles(List userInfoList) { + Map> userRoles = new HashMap<>(); + for (UserInfo userInfo: userInfoList) { + List roles = userInfo.getRoles().stream().filter(UserRole::isHasRole) + .map(UserRole::getName).collect(Collectors.toList()); + userRoles.put(userInfo.getEmail(), roles); + } + return userRoles; + } + private static int createTestInstance(String instanceName, int studyGroupId) { int instanceId = createInstance(instanceName); SimpleResult res = inTransaction(conn -> { From 9a7296866bebb3e92483546ca207545a1c42b5cb Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 18 Jul 2023 15:20:12 -0400 Subject: [PATCH 23/35] WIP --- .../org/broadinstitute/dsm/DSMServer.java | 14 +- .../dsm/db/dao/user/UserDao.java | 2 +- .../dsm/route/admin/StudyRoleRoute.java | 41 ++ .../dsm/route/admin/UserRoleRoute.java | 76 +++- .../dsm/route/admin/UserRoute.java | 67 ++-- .../dsm/service/admin/AddUserRequest.java | 33 +- .../dsm/service/admin/StudyRoleRequest.java | 25 -- .../dsm/service/admin/StudyRoleResponse.java | 31 +- .../dsm/service/admin/UserAdminService.java | 365 ++++++++++-------- .../broadinstitute/dsm/statics/RoutePath.java | 5 +- .../service/admin/UserAdminServiceTest.java | 139 +++++-- 11 files changed, 503 insertions(+), 295 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/StudyRoleRoute.java delete mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 53cabf8708..5f93682b6d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -121,8 +121,10 @@ import org.broadinstitute.dsm.route.TriggerSurveyRoute; import org.broadinstitute.dsm.route.UserSettingRoute; import org.broadinstitute.dsm.route.ViewFilterRoute; +import org.broadinstitute.dsm.route.admin.StudyRoleRoute; import org.broadinstitute.dsm.route.admin.UserRoleRoute; import org.broadinstitute.dsm.route.admin.RegisterParticipantRoute; +import org.broadinstitute.dsm.route.admin.UserRoute; import org.broadinstitute.dsm.route.dashboard.NewDashboardRoute; import org.broadinstitute.dsm.route.familymember.AddFamilyMemberRoute; import org.broadinstitute.dsm.route.kit.KitFinalScanRoute; @@ -670,6 +672,8 @@ protected void setupCustomRouting(@NonNull Config cfg) { setupMiscellaneousRoutes(); + setupAdminRoutes(); + setupSharedRoutes(kitUtil, notificationUtil, patchUtil); setupCohortTagRoutes(); @@ -918,18 +922,26 @@ private void setupMiscellaneousRoutes() { GetParticipantDataRoute getParticipantDataRoute = new GetParticipantDataRoute(); get(uiRoot + RoutePath.GET_PARTICIPANT_DATA, getParticipantDataRoute, new JsonTransformer()); + } + private void setupAdminRoutes() { RegisterParticipantRoute registerParticipantRoute = new RegisterParticipantRoute(); post(uiRoot + RoutePath.REGISTER_PARTICIPANT, registerParticipantRoute, new JsonTransformer()); + StudyRoleRoute studyRoleRoute = new StudyRoleRoute(); + get(uiRoot + RoutePath.STUDY_ROLE, studyRoleRoute, new JsonTransformer()); + UserRoleRoute userRoleRoute = new UserRoleRoute(); + get(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); post(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); + delete(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer()); - UserRoleRoute userRoute = new UserRoleRoute(); + UserRoute userRoute = new UserRoute(); post(uiRoot + RoutePath.USER, userRoute, new JsonTransformer()); delete(uiRoot + RoutePath.USER, userRoute, new JsonTransformer()); } + private void setupSomaticUploadRoutes(@NonNull Config cfg) { SomaticResultUploadService somaticResultUploadService = SomaticResultUploadService.fromConfig(cfg); post(uiRoot + RoutePath.SOMATIC_DOCUMENT_ROUTE, diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index bd6603e6c5..c8974aa645 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -80,7 +80,7 @@ public Optional get(long userId) { @Override public int create(UserDto userDto) { SimpleResult results = inTransaction((conn) -> { - SimpleResult execResult = new SimpleResult(); + SimpleResult execResult = new SimpleResult(-1); try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, PreparedStatement.RETURN_GENERATED_KEYS)) { stmt.setString(1, userDto.getName().orElse("")); stmt.setString(2, userDto.getEmail().orElse("")); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/StudyRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/StudyRoleRoute.java new file mode 100644 index 0000000000..2e38efb1b1 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/StudyRoleRoute.java @@ -0,0 +1,41 @@ +package org.broadinstitute.dsm.route.admin; + +import lombok.extern.slf4j.Slf4j; +import org.broadinstitute.dsm.exception.DSMBadRequestException; +import org.broadinstitute.dsm.exception.DsmInternalError; +import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.service.admin.StudyRoleResponse; +import org.broadinstitute.dsm.service.admin.UserAdminService; +import org.broadinstitute.dsm.statics.RoutePath; +import org.broadinstitute.lddp.handlers.util.Result; +import spark.Request; +import spark.Response; + +@Slf4j +public class StudyRoleRoute extends RequestHandler { + + @Override + public Object processRequest(Request request, Response response, String userId) { + String studyGroup; + try { + studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + } catch (Exception e) { + return UserRoleRoute.handleError(e, "getting study group", response); + } + + UserAdminService adminService = new UserAdminService(userId, studyGroup); + + if (!request.requestMethod().equals(RoutePath.RequestMethod.GET.toString())) { + String msg = "Invalid HTTP method for UserRoleRoute: " + request.requestMethod(); + log.error(msg); + response.status(500); + return msg; + } + + try { + return adminService.getStudyRoles(); + } catch (Exception e) { + return UserRoleRoute.handleError(e, "getting study roles", response); + } + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java index f25b589abc..4a9b1b6358 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoleRoute.java @@ -6,6 +6,7 @@ import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.security.RequestHandler; +import org.broadinstitute.dsm.service.admin.UserRequest; import org.broadinstitute.dsm.service.admin.UserRoleRequest; import org.broadinstitute.dsm.service.admin.UserAdminService; import org.broadinstitute.dsm.statics.RoutePath; @@ -18,11 +19,38 @@ public class UserRoleRoute extends RequestHandler { @Override public Object processRequest(Request request, Response response, String userId) { + String studyGroup; + try { + studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + } catch (Exception e) { + return handleError(e, "getting study group", response); + } - String studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + UserAdminService service = new UserAdminService(userId, studyGroup); + String requestMethod = request.requestMethod(); String body = request.body(); - if (StringUtils.isBlank(body)) { + boolean hasBody = !StringUtils.isBlank(body); + + if (requestMethod.equals(RoutePath.RequestMethod.GET.toString())) { + UserRequest req = null; + if (hasBody) { + try { + req = new Gson().fromJson(body, UserRequest.class); + } catch (Exception e) { + log.info("Invalid request format for {}", body); + response.status(400); + return "Invalid request format"; + } + } + try { + return service.getUserRoles(req); + } catch (Exception e) { + return handleError(e, "getting user roles", response); + } + } + + if (!hasBody) { response.status(400); return "Request body is blank"; } @@ -30,33 +58,26 @@ public Object processRequest(Request request, Response response, String userId) UserRoleRequest req; try { req = new Gson().fromJson(body, UserRoleRequest.class); - log.info("TEMP: UserRoleRequest {}", req); } catch (Exception e) { log.info("Invalid request format for {}", body); response.status(400); return "Invalid request format"; } - UserAdminService adminService = new UserAdminService(userId, studyGroup); - - if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { + if (requestMethod.equals(RoutePath.RequestMethod.POST.toString())) { try { - adminService.addUserToRoles(req); - } catch (DSMBadRequestException e) { - response.status(400); - return e.getMessage(); - } catch (DsmInternalError e) { - log.error("Error adding users to roles: {}", e.getMessage()); - response.status(500); - return "Internal error. Contact development team"; + service.addUserRoles(req); } catch (Exception e) { - log.error("Error adding users to roles: {}", e.getMessage()); - response.status(500); - return e.getMessage(); + return handleError(e, "adding user roles", response); + } + } else if (requestMethod.equals(RoutePath.RequestMethod.DELETE.toString())) { + try { + service.removeUserRoles(req); + } catch (Exception e) { + return handleError(e, "removing user roles", response); } - //else if (request.requestMethod().equals(RoutePath.RequestMethod.DELETE.toString())) { } else { - String msg = "Invalid HTTP method for UserRoleRoute"; + String msg = "Invalid HTTP method for UserRoleRoute: " + requestMethod; log.error(msg); response.status(500); return msg; @@ -64,4 +85,21 @@ public Object processRequest(Request request, Response response, String userId) return new Result(200); } + + protected static String handleError(Throwable e, String operation, Response response) { + if (e instanceof DSMBadRequestException) { + response.status(400); + log.info("DSMBadRequestException {}: {}", operation, e.getMessage()); + return e.getMessage(); + } else if (e instanceof DsmInternalError) { + log.error("Error {}: {}", operation, e.getMessage()); + response.status(500); + return "Internal error. Contact development team"; + } + + // any other exception + log.error("Error {}: {}", operation, e.getMessage()); + response.status(500); + return e.getMessage(); + } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java index 9db8f136d7..fcb49e3906 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java @@ -3,11 +3,10 @@ import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.broadinstitute.dsm.exception.DSMBadRequestException; -import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.security.RequestHandler; -import org.broadinstitute.dsm.service.admin.UserAdminService; import org.broadinstitute.dsm.service.admin.AddUserRequest; +import org.broadinstitute.dsm.service.admin.UserAdminService; +import org.broadinstitute.dsm.service.admin.UserRequest; import org.broadinstitute.dsm.statics.RoutePath; import org.broadinstitute.lddp.handlers.util.Result; import spark.Request; @@ -18,8 +17,12 @@ public class UserRoute extends RequestHandler { @Override public Object processRequest(Request request, Response response, String userId) { - - String studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + String studyGroup; + try { + studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap()); + } catch (Exception e) { + return UserRoleRoute.handleError(e, "getting study group", response); + } String body = request.body(); if (StringUtils.isBlank(body)) { @@ -27,49 +30,39 @@ public Object processRequest(Request request, Response response, String userId) return "Request body is blank"; } - AddUserRequest req; - try { - req = new Gson().fromJson(body, AddUserRequest.class); - } catch (Exception e) { - log.info("Invalid request format for {}", body); - response.status(400); - return "Invalid request format"; - } - UserAdminService adminService = new UserAdminService(userId, studyGroup); + String requestMethod = request.requestMethod(); - if (request.requestMethod().equals(RoutePath.RequestMethod.POST.toString())) { + if (requestMethod.equals(RoutePath.RequestMethod.POST.toString())) { + AddUserRequest req; try { - adminService.createUser(req); - } catch (DSMBadRequestException e) { + req = new Gson().fromJson(body, AddUserRequest.class); + } catch (Exception e) { + log.info("Invalid request format for {}", body); response.status(400); - return e.getMessage(); - } catch (DsmInternalError e) { - log.error("Error adding user: {}", e.getMessage()); - response.status(500); - return "Internal error. Contact development team"; + return "Invalid request format"; + } + try { + adminService.addUser(req); } catch (Exception e) { - log.error("Error adding user: {}", e.getMessage()); - response.status(500); - return e.getMessage(); + return UserRoleRoute.handleError(e, "adding user", response); } - } else if (request.requestMethod().equals(RoutePath.RequestMethod.DELETE.toString())) { + } else if (requestMethod.equals(RoutePath.RequestMethod.DELETE.toString())) { + UserRequest req; try { - adminService.removeUser(req); - } catch (DSMBadRequestException e) { + req = new Gson().fromJson(body, UserRequest.class); + } catch (Exception e) { + log.info("Invalid request format for {}", body); response.status(400); - return e.getMessage(); - } catch (DsmInternalError e) { - log.error("Error removing user: {}", e.getMessage()); - response.status(500); - return "Internal error. Contact development team"; + return "Invalid request format"; + } + try { + adminService.removeUser(req); } catch (Exception e) { - log.error("Error removing user: {}", e.getMessage()); - response.status(500); - return e.getMessage(); + return UserRoleRoute.handleError(e, "removing user", response); } } else { - String msg = "Invalid HTTP method for UserRoute"; + String msg = "Invalid HTTP method for UserRoute: " + requestMethod; log.error(msg); response.status(500); return msg; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java index 31bacd61bb..f4174f937e 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java @@ -8,19 +8,28 @@ @Data public class AddUserRequest { - private final String email; - private final String name; - private final String phone; - private final List roles; - - public AddUserRequest(String email, String name, String phone, List roles) { - this.email = email; - this.name = name; - this.phone = phone; - this.roles = roles; + private final List users; + + public AddUserRequest(List users) { + this.users = users; } - public UserDto asUserDto() { - return new UserDto(-1, name, email, phone); + @Data + public static class User { + private final String email; + private final String name; + private final String phone; + private final List roles; + + public User(String email, String name, String phone, List roles) { + this.email = email; + this.name = name; + this.phone = phone; + this.roles = roles; + } + + public UserDto asUserDto() { + return new UserDto(-1, name, email, phone); + } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java deleted file mode 100644 index f9401d8fbf..0000000000 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.broadinstitute.dsm.service.admin; - -import java.util.List; - -import lombok.Data; - - -@Data -public class StudyRoleRequest { - private final List roles; - - public StudyRoleRequest(List roles) { - this.roles = roles; - } - - public static class RoleInfo { - public final String roleName; - public final String adminRoleName; - - public RoleInfo(String roleName, String adminRoleName) { - this.roleName = roleName; - this.adminRoleName = adminRoleName; - } - } -} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java index a2289700f4..2b410a4cfa 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/StudyRoleResponse.java @@ -1,11 +1,34 @@ package org.broadinstitute.dsm.service.admin; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data public class StudyRoleResponse { - private Map studyRoles; + private List roles; + + public StudyRoleResponse(List roles) { + this.roles = roles; + } + + public StudyRoleResponse() { + this.roles = new ArrayList<>(); + } + + public void addRole(StudyRoleResponse.Role role) { + this.roles.add(role); + } + + @Data + public static class Role { + private final String name; + private final String displayText; - public StudyRoleResponse(Map studyRoles) { - this.studyRoles = studyRoles; + public Role(String name, String displayText) { + this.name = name; + this.displayText = displayText; + } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 7ee23ad898..bc6afbd20a 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -16,6 +16,8 @@ import java.util.Set; import java.util.stream.Collectors; +import liquibase.pro.packaged.S; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.broadinstitute.dsm.db.dao.user.UserDao; @@ -57,12 +59,6 @@ public class UserAdminService { + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " + "WHERE aurg.group_id = ?"; - private static final String SQL_SELECT_ROLES_FOR_GROUP_AND_USER = - "SELECT ar.role_id, ar.name, dg.group_id FROM access_role ar " - + "JOIN access_user_role_group aurg on aurg.role_id = ar.role_id " - + "JOIN ddp_group dg on dg.group_id = aurg.group_id " - + "WHERE aurg.user_id = ? AND dg.group_id = ?"; - private static final String SQL_SELECT_USER_ROLE = "SELECT aurg.user_role_group_id FROM access_user_role_group aurg " + "JOIN access_role ar on ar.role_id = aurg.role_id " @@ -73,14 +69,14 @@ public class UserAdminService { private static final String SQL_SELECT_ROLE = "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; - private static final String SQL_SELECT_ROLE_FOR_ADMIN = - "SELECT ar.role_id FROM access_role ar WHERE ar.name = ?"; + private static final String SQL_SELECT_GROUP_ROLE = + "SELECT dgr.group_role_id FROM ddp_group_role dgr WHERE dgr.group_id = ? AND dgr.role_id = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = - "SELECT au.user_id, au.name FROM access_user au WHERE au.email = ?"; + "SELECT au.user_id, au.name, au.phone_number, au.is_active FROM access_user au WHERE au.email = ?"; private static final String SQL_SELECT_USERS_FOR_GROUP = - "SELECT au.user_id, au.name, au.email, au.phone_number FROM access_user au " + "SELECT au.user_id, au.email, au.name, au.phone_number FROM access_user au " + "JOIN access_user_role_group aurg on aurg.user_id = au.user_id " + "JOIN ddp_group dg on dg.group_id = aurg.group_id " + "WHERE dg.group_id = ? AND au.is_active = 1"; @@ -97,9 +93,6 @@ public class UserAdminService { private static final String SQL_DELETE_USER_ROLE = "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ? AND group_id = ?"; - private static final String SQL_INSERT_ROLE = - "INSERT INTO access_role SET name = ?"; - private static final String SQL_INSERT_GROUP = "INSERT INTO ddp_group SET name = ?"; @@ -109,7 +102,7 @@ public class UserAdminService { private static final String SQL_INSERT_GROUP_ROLE = "INSERT INTO ddp_group_role SET group_id = ?, role_id = ?, admin_role_id = ?"; - public static final String SQL_SELECT_GROUP_FOR_REALM = + private static final String SQL_SELECT_GROUP_FOR_REALM = "SELECT dg.group_id, dg.name from ddp_group dg " + "JOIN ddp_instance_group dig on dig.ddp_group_id = dg.group_id " + "JOIN ddp_instance di on di.ddp_instance_id = dig.ddp_instance_id " @@ -120,8 +113,26 @@ public UserAdminService(String operatorId, String studyGroup) { this.studyGroup = studyGroup; } - public void addUserToRoles(UserRoleRequest req) { - validateRoleRequest(req, true); + /** + * Get study roles that operator can administer + */ + public StudyRoleResponse getStudyRoles() { + int groupId = verifyStudyGroup(studyGroup); + Map studyRoles = getAdminRoles(groupId); + + StudyRoleResponse res = new StudyRoleResponse(); + for (var entry: studyRoles.entrySet()) { + RoleInfo roleInfo = entry.getValue(); + res.addRole(new StudyRoleResponse.Role(roleInfo.getName(), roleInfo.getDisplayText())); + } + return res; + } + + /** + * Add roles for list of users + */ + public void addUserRoles(UserRoleRequest req) { + validateRoleRequest(req); int groupId = verifyStudyGroup(studyGroup); Map studyRoles = getAdminRoles(groupId); @@ -132,12 +143,12 @@ public void addUserToRoles(UserRoleRequest req) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); } - addUserRoles(userEmail, roleNames, groupId, studyRoles); + addRoles(userEmail, roleNames, groupId, studyRoles); } } - protected void addUserRoles(String email, List roles, int groupId, Map studyRoles) { - int userId = getUserByEmail(email, groupId); + protected void addRoles(String email, List roles, int groupId, Map studyRoles) { + int userId = verifyUserByEmail(email, groupId); for (String role : roles) { if (StringUtils.isBlank(role)) { @@ -156,8 +167,11 @@ protected void addUserRoles(String email, List roles, int groupId, Map studyRoles = getAdminRoles(groupId); @@ -174,7 +188,7 @@ public void removeUserFromRoles(UserRoleRequest req) { protected void removeUserRoles(String email, List roles, int groupId, Map studyRoles) { // TODO loop is similar to addUserRoles -DC - int userId = getUserByEmail(email, groupId); + int userId = verifyUserByEmail(email, groupId); for (String role : roles) { if (StringUtils.isBlank(role)) { @@ -194,16 +208,12 @@ protected void removeUserRoles(String email, List roles, int groupId, Ma } } - protected void validateRoleRequest(UserRoleRequest req, boolean includesRoles) { + protected void validateRoleRequest(UserRoleRequest req) { if (CollectionUtils.isEmpty(req.getUsers())) { throw new DSMBadRequestException("Invalid users: empty"); } if (CollectionUtils.isEmpty(req.getRoles())) { - if (includesRoles) { - throw new DSMBadRequestException("Invalid roles: empty"); - } - } else if (!includesRoles) { - throw new DSMBadRequestException("Invalid roles: should be empty"); + throw new DSMBadRequestException("Invalid roles: empty"); } } @@ -211,50 +221,95 @@ protected void validateRoles(List roleNames, Set validRoleNames) if (validRoleNames.containsAll(roleNames)) { return; } - Collection badRoles = CollectionUtils.subtract(validRoleNames, roleNames); + Collection badRoles = CollectionUtils.subtract(roleNames, validRoleNames); String msg = String.format("Invalid roles for study group %s: %s", studyGroup, String.join(",", badRoles)); throw new DSMBadRequestException(msg); } - public void createUser(AddUserRequest req) { - if (StringUtils.isBlank(req.getName())) { - throw new DSMBadRequestException("Invalid user name: blank"); - } - if (StringUtils.isBlank(req.getEmail())) { + protected String validateEmailRequest(String email) { + if (StringUtils.isBlank(email)) { throw new DSMBadRequestException("Invalid user email: blank"); } - // TODO: Currently we do not assign users to study groups, but when we do validate UserRequest.studyGroup -DC + return email; + } + + /** + * Add user to study group. User request must include at least one role + */ + public void addUser(AddUserRequest req) { + List users = req.getUsers(); + if (CollectionUtils.isEmpty(users)) { + throw new DSMBadRequestException("Invalid user list: blank"); + } + + // pre-check to lessen likelihood of partial operation + for (var user: users) { + String email = validateEmailRequest(user.getEmail()); + + // not a strict requirement in DB, but now enforcing + if (StringUtils.isBlank(user.getName())) { + throw new DSMBadRequestException("Invalid user name: blank"); + } + if (getUserByEmail(email, -1) != null) { + throw new DsmInternalError("User already exists: " + email); + } + } UserDao userDao = new UserDao(); - userDao.create(req.asUserDto()); + for (var user: users) { + int userId = userDao.create(user.asUserDto()); + if (userId == -1) { + throw new DsmInternalError("Error creating user: " + user.getEmail()); + } + } } - public void removeUser(AddUserRequest req) { - if (StringUtils.isBlank(req.getEmail())) { - throw new DSMBadRequestException("Invalid user email: blank"); + /** + * Remove one or more users and their associated roles + */ + public void removeUser(UserRequest req) { + if (CollectionUtils.isEmpty(req.getUsers())) { + throw new DSMBadRequestException("Invalid user list: blank"); + } + + // pre-check all users to decrease likelihood of incomplete operation + List userIds = new ArrayList<>(); + for (String email: req.getUsers()) { + validateEmailRequest(email); + userIds.add(verifyUserByEmail(email, -1)); } - int userId = getUserByEmail(req.getEmail(), -1); - deleteUserRoles(userId); UserDao userDao = new UserDao(); - userDao.delete(userId); + for (int userId: userIds) { + deleteUserRoles(userId); + userDao.delete(userId); + } } + /** + * Return user information and user roles, both assigned and unassigned roles for study + * + * @param req list of users, or all study users if NULL + */ public UserRoleResponse getUserRoles(UserRequest req) { int groupId = verifyStudyGroup(studyGroup); Map studyRoles = getAdminRoles(groupId); - // get access_user info for the users + // get userId to UserInfo for the users + // if list of users provided, get roles for just those users Map studyUsers = getStudyUsers(groupId, req); - // if list of users provided, create a list of just those users - // get roles for users (should include admin predictate) + // get current roles for all study users Map> rolesByUser = getRolesForStudyUsers(groupId); + // combine user info and role info UserRoleResponse res = new UserRoleResponse(); for (var entry: studyUsers.entrySet()) { - UserInfo userInfo = entry.getValue(); Set roles = rolesByUser.get(entry.getKey()); - List userRoles = createUserRoles(roles, studyRoles); + if (roles == null) { + roles = new HashSet<>(); + } + List userRoles = convertToUserRoles(roles, studyRoles); + UserInfo userInfo = entry.getValue(); userInfo.addRoles(userRoles); res.addUser(userInfo); } @@ -270,19 +325,26 @@ protected static Map getStudyUsers(int groupId, UserRequest r Map emailToId = allStudyUsers.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getEmail(), Map.Entry::getKey)); - Map users = new HashMap<>(); for (String email: req.getUsers()) { Integer id = emailToId.get(email); - if (id == null) { - throw new DSMBadRequestException("Invalid user email " + email); + if (id != null) { + users.put(id, allStudyUsers.get(id)); + } else { + // theoretically there should be no users without roles for a given study + // but there are ways that might occur. + log.warn("Found user with no study roles: {}", email); + StudyUser user = getUserByEmail(email, groupId); + if (user == null) { + throw new DSMBadRequestException("Invalid user email " + email); + } + users.put(user.getId(), user.toUserInfo()); } - users.put(id, allStudyUsers.get(id)); } return users; } - protected static List createUserRoles(Set roles, Map studyRoles) { + protected static List convertToUserRoles(Set roles, Map studyRoles) { List userRoles = new ArrayList<>(); for (var entry: studyRoles.entrySet()) { @@ -292,42 +354,6 @@ protected static List createUserRoles(Set roles, Map adminRoles = getAdminRoles(groupId); - if (!adminRoles.containsKey(PEPPER_ADMIN_ROLE)) { - throw new DSMBadRequestException("Operator does not have add study role privileges for study group " - + studyGroup); - } - - List roles = req.getRoles(); - if (CollectionUtils.isEmpty(roles)) { - throw new DSMBadRequestException("No roles provided"); - } - - for (StudyRoleRequest.RoleInfo roleInfo: roles) { - String role = roleInfo.roleName; - if (StringUtils.isBlank(role)) { - throw new DSMBadRequestException("Invalid role name: blank"); - } - int roleId = getRoleId(role); - if (roleId == -1) { - throw new DSMBadRequestException("Invalid role name: " + role); - } - - int adminRoleId = -1; - String adminRole = roleInfo.adminRoleName; - if (!StringUtils.isBlank(adminRole)) { - adminRoleId = getRoleId(adminRole); - if (adminRoleId == -1) { - throw new DSMBadRequestException("Invalid admin role name: " + adminRole); - } - } - addGroupRole(groupId, roleId, adminRoleId); - } - } - protected Map getAdminRoles(int studyGroupId) { int adminId; try { @@ -335,14 +361,16 @@ protected Map getAdminRoles(int studyGroupId) { } catch (NumberFormatException e) { throw new DSMBadRequestException("Invalid operator ID format: " + operatorId); } - Map adminRoles = verifyOperatorGroup(adminId, studyGroupId); - if (adminRoles == null || adminRoles.isEmpty()) { - throw new DSMBadRequestException("Operator does not have user administrator privileges for study group " - + studyGroup); - } - return adminRoles; + return verifyOperatorAdminRoles(adminId, studyGroupId); } + /** + * Get valid study group based on 'realm' or 'studyGroup' query parameters + * + * @param queryParams HTTP request query params + * @return valid study name + * @throws DSMBadRequestException if 'realm' or 'studyGroup' are invalid or mismatched + */ public static String getStudyGroup(Map queryParams) { String studyGroup = null; String realmGroup = null; @@ -368,33 +396,29 @@ public static String getStudyGroup(Map queryParams) { } protected static String getStudyGroupForRealm(String realm) { - NameAndId res = _getStudyGroupForRealm(realm); + NameAndId res = studyGroupForRealm(realm); if (res == null) { throw new DSMBadRequestException("Invalid realm: " + realm); } return res.name; } - private static NameAndId _getStudyGroupForRealm(String realm) { - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); + private static NameAndId studyGroupForRealm(String realm) { + return inTransaction(conn -> { + NameAndId res = null; try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP_FOR_REALM)) { stmt.setString(1, realm); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - dbVals.resultValue = new NameAndId(rs.getString(2), rs.getInt(1)); + res = new NameAndId(rs.getString(2), rs.getInt(1)); } } } catch (SQLException e) { - dbVals.resultException = e; + String msg = String.format("Error getting study group for realm %s", realm); + throw new DsmInternalError(msg, e); } - return dbVals; + return res; }); - - if (res.resultException != null) { - throw new DsmInternalError("Error getting roles groups for user", res.resultException); - } - return (NameAndId) res.resultValue; } protected static int verifyStudyGroup(String studyGroup) { @@ -417,11 +441,19 @@ protected static int verifyStudyGroup(String studyGroup) { }); } - protected static Map verifyOperatorGroup(int operatorId, int groupId) { + protected Map verifyOperatorAdminRoles(int operatorId, int groupId) { + Map adminRoles = getOperatorAdminRoles(operatorId, groupId); + if (adminRoles == null || adminRoles.isEmpty()) { + throw new DSMBadRequestException("Operator does not have user administrator privileges for study group " + + studyGroup); + } + return adminRoles; + } + + protected static Map getOperatorAdminRoles(int operatorId, int groupId) { Map adminRoles = null; List roles = getRolesForUser(operatorId, groupId); if (roles.isEmpty()) { - log.info("No roles found for operator {} and group {}", operatorId, groupId); return adminRoles; } @@ -498,18 +530,6 @@ protected static void setAdminForRole(int roleId, int adminRoleId, int groupId) } - protected int verifyRoleForAdmin(String role, Map adminRoles) { - int roleId = getRoleId(role); - if (roleId == -1 || !adminRoles.containsKey(role)) { - String msg = String.format("Invalid role %s for study group %s", role, studyGroup); - if (roleId != -1) { - log.info("{}: role is valid but admin does not have permission", msg); - } - throw new DSMBadRequestException(msg); - } - return roleId; - } - protected static Map> getRolesForStudyUsers(int groupId) { return inTransaction(conn -> { Map> roles = new HashMap<>(); @@ -548,34 +568,32 @@ protected static int getRoleId(String role) { }); } - protected static int getUserByEmail(String email, int groupId) { - // TODO: Currently we do not track the valid roles for a group, but get by - // groupId once we do -DC - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); + protected static int verifyUserByEmail(String email, int groupId) { + StudyUser user = getUserByEmail(email, groupId); + // TODO: see if access_user.is_active is used -DC + if (user == null) { + throw new DSMBadRequestException("Invalid user for study group: " + email); + } + return user.getId(); + } + + protected static UserAdminService.StudyUser getUserByEmail(String email, int groupId) { + // TODO: Currently we do not track users for a group, but get by groupId once we do -DC + return inTransaction(conn -> { + UserAdminService.StudyUser user = null; try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_BY_EMAIL)) { stmt.setString(1, email); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - dbVals.resultValue = rs.getInt(1); + user = new StudyUser(rs.getInt(1), email, rs.getString(2), rs.getString(3)); } } } catch (SQLException e) { - dbVals.resultException = e; + String msg = String.format("Error getting user %s for study group, ID: %d", email, groupId); + throw new DsmInternalError(msg, e); } - return dbVals; + return user; }); - - if (res.resultException != null) { - String msg = String.format("Error getting user %s for study group, ID: %d", email, groupId); - throw new DsmInternalError(msg, res.resultException); - } - - if (res.resultValue == null) { - throw new DSMBadRequestException("Invalid user for study group: " + email); - } - return (int) res.resultValue; - // TODO: see if access_user.is_active is used -DC } protected static int getUserRole(int userId, int roleId, int groupId) { @@ -627,41 +645,60 @@ protected static Map getUsersForGroup(int groupId) { }); } - protected static int addUserRole(int userId, int roleId, int groupId) throws Exception { + protected static int addUserRole(int userId, int roleId, int groupId) { // since we do not have a key constraint for duplicate entries we need to check first int userRoleId = getUserRole(userId, roleId, groupId); if (userRoleId != -1) { return userRoleId; } - SimpleResult res = inTransaction(conn -> { - SimpleResult dbVals = new SimpleResult(); + return inTransaction(conn -> { + int id = -1; try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER_ROLE, Statement.RETURN_GENERATED_KEYS)) { stmt.setInt(1, userId); stmt.setInt(2, roleId); stmt.setInt(3, groupId); int result = stmt.executeUpdate(); if (result != 1) { - dbVals.resultException = new DsmInternalError("Error adding user to role. Result count was " + result); + throw new DsmInternalError("Error adding user to role. Result count was " + result); } try (ResultSet rs = stmt.getGeneratedKeys()) { if (rs.next()) { - dbVals.resultValue = rs.getInt(1); + id = rs.getInt(1); } } } catch (SQLException ex) { - dbVals.resultException = ex; + throw new DsmInternalError("Error adding user to role", ex); } - return dbVals; + return id; }); + } - if (res.resultException != null) { - throw res.resultException; - } - return (int) res.resultValue; + protected static int getGroupRole(int groupId, int roleId) { + return inTransaction(conn -> { + int id = -1; + try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_GROUP_ROLE)) { + stmt.setInt(1, groupId); + stmt.setInt(2, roleId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + id = rs.getInt(1); + } + } + } catch (SQLException e) { + String msg = String.format("Error getting group role: groupId=%d, roleId=%s", groupId, roleId); + throw new DsmInternalError(msg, e); + } + return id; + }); } protected static int addGroupRole(int groupId, int roleId, int adminRoleId) { - String errMsg = "Error adding group role: groupId=%d, roleId=%d, adminRoleId=%d. "; + String errMsg = String.format("Error adding group role: groupId=%d, roleId=%d, adminRoleId=%d. ", + groupId, roleId, adminRoleId); + int groupRoleId = getGroupRole(groupId, roleId); + if (groupRoleId != -1) { + throw new DsmInternalError(errMsg + "Group role already exists"); + } int res = inTransaction(conn -> { int id = -1; try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_GROUP_ROLE, Statement.RETURN_GENERATED_KEYS)) { @@ -771,10 +808,11 @@ protected static int deleteStudyGroup(int groupId) { } + @Data protected static class RoleInfo { - public final int roleId; - public final String name; - public final String displayText; + private final int roleId; + private final String name; + private final String displayText; public RoleInfo(int roleId, String name, String displayText) { this.roleId = roleId; @@ -783,6 +821,25 @@ public RoleInfo(int roleId, String name, String displayText) { } } + @Data + protected static class StudyUser { + private final int id; + private final String email; + private final String name; + private final String phone; + + public StudyUser(int id, String email, String name, String phone) { + this.id = id; + this.email = email; + this.name = name; + this.phone = phone; + } + + public UserInfo toUserInfo() { + return new UserInfo(email, name, phone); + } + } + protected static class NameAndId { public final String name; public final int id; diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index 445dc62e75..74f7898aa9 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -97,8 +97,9 @@ public class RoutePath { public static final String GET_PARTICIPANT = "getParticipant"; public static final String GET_PARTICIPANT_DATA = "getParticipantData"; public static final String REGISTER_PARTICIPANT = "registerParticipant"; - public static final String USER = "user"; - public static final String USER_ROLE = "userRole"; + public static final String USER = "admin/user"; + public static final String USER_ROLE = "admin/userRole"; + public static final String STUDY_ROLE = "admin/studyRole"; public static final String TISSUE_LIST = "tissueList"; public static final String NDI_REQUEST = "ndiRequest"; public static final String ABSTRACTION_FORM_CONTROLS = "abstractionformcontrols"; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 532946ac1f..09b49da320 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -100,7 +101,7 @@ private int createUser(String email) { return userDao.create(userDto); } - private int addUserRole(int userId, int roleId, int groupId) throws Exception { + private int addUserRole(int userId, int roleId, int groupId) { int userRoleId = UserAdminService.addUserRole(userId, roleId, groupId); addRoleForUser(roleId, userId); return userRoleId; @@ -131,23 +132,26 @@ private void removeGroupRole(int groupRoleId) { createdGroupRoles.remove(groupRoleId); } - @Test - public void testgetRoleId() { + public void testGetRoleId() { + // temporary to understand which roles are handled by liquibase + log.info("TEMP: all roles: {}", String.join("\n", getAllRoles())); int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); } @Test public void testVerifyOperatorGroup() { - int roleId = UserAdminService.getRoleId("upload_onc_history"); + int roleId = UserAdminService.getRoleId("dashboard_view"); Assert.assertTrue(roleId > 0); int userId = createTestUser("test_admin1@study.org", roleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + + UserAdminService service = new UserAdminService(Integer.toString(userId), TEST_GROUP); try { - UserAdminService.verifyOperatorGroup(userId, groupId); + service.verifyOperatorAdminRoles(userId, groupId); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("No roles found")); + Assert.assertTrue(e.getMessage().contains("does not have user administrator privileges")); } try { @@ -157,27 +161,30 @@ public void testVerifyOperatorGroup() { } try { - Map adminRoles = UserAdminService.verifyOperatorGroup(userId, groupId); - Assert.assertTrue(adminRoles.isEmpty()); + Map adminRoles = UserAdminService.getOperatorAdminRoles(userId, groupId); + Assert.assertTrue(adminRoles == null || adminRoles.isEmpty()); } catch (Exception e) { - Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + Assert.fail("Exception from UserAdminService.getOperatorAdminRoles: " + getStackTrace(e)); } try { - Map adminRoles = UserAdminService.verifyOperatorGroup(userId, groupId); + service.verifyOperatorAdminRoles(userId, groupId); Assert.fail("Expecting exception from UserAdminService.verifyOperatorForGroup"); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("does not have administrator privileges")); + Assert.assertTrue(e.getMessage().contains("does not have user administrator privileges")); } - int adminRoleId = UserAdminService.getRoleId(USER_ADMIN_ROLE); + // give the admin user admin privileges, and a role to manage try { - addUserRole(userId, adminRoleId, groupId); + addUserRole(userId, userAdminRoleId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } + + addGroupRole(roleId, userAdminRoleId); + try { - UserAdminService.verifyOperatorGroup(userId, groupId); + service.verifyOperatorAdminRoles(userId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } @@ -191,7 +198,7 @@ public void testGetUserByEmailAndGroup() { int userId = createTestUser(email, -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - int id = UserAdminService.getUserByEmail(email, groupId); + int id = UserAdminService.verifyUserByEmail(email, groupId); Assert.assertEquals(userId, id); } catch (Exception e) { Assert.fail("Exception from UserAdminService.getUserByEmail: " + getStackTrace(e)); @@ -203,10 +210,12 @@ public void testUserRoles() { int operatorId = createAdminUser("test_admin2@study.org", userAdminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - UserAdminService.verifyOperatorGroup(operatorId, groupId); + Map adminRoles = UserAdminService.getOperatorAdminRoles(operatorId, groupId); + Assert.assertTrue(adminRoles == null || adminRoles.isEmpty()); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } + String role1 = "upload_onc_history"; int roleId1 = UserAdminService.getRoleId(role1); Assert.assertTrue(roleId1 > 0); @@ -222,25 +231,35 @@ public void testUserRoles() { int userId2 = createTestUser(user2, roleId1); addRoleForUser(roleId2, userId2); - List users = List.of(user1, user2); - - UserRoleRequest req = new UserRoleRequest(users, roles); + // let operator manage one of the roles + addGroupRole(roleId1, userAdminRoleId); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); + + List users = List.of(user1, user2); + UserRoleRequest req = new UserRoleRequest(users, roles); try { - service.addUserToRoles(req); + service.addUserRoles(req); Assert.fail("UserAdminService.addUserToRoles should fail with roles not in study"); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("Invalid user")); + Assert.assertTrue(e.getMessage().contains("Invalid roles for study group")); } - int groupRoleId1 = addGroupRole(roleId1, userAdminRoleId); - int groupRoleId2 = addGroupRole(roleId2, userAdminRoleId); + // both roles are now in the study + addGroupRole(roleId2, userAdminRoleId); + + // verify by getting all study roles + Set allRoles = Set.of(role1, role2); + StudyRoleResponse srRes = service.getStudyRoles(); + Assert.assertEquals(srRes.getRoles().stream().map(StudyRoleResponse.Role::getName).collect(Collectors.toSet()), allRoles); + try { - service.addUserToRoles(req); + service.addUserRoles(req); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserToRole: " + getStackTrace(e)); } + + // verify internally int userRoleId = UserAdminService.getUserRole(userId1, roleId1, groupId); Assert.assertNotEquals(-1, userRoleId); int userRoleId2 = UserAdminService.getUserRole(userId1, roleId2, groupId); @@ -251,41 +270,62 @@ public void testUserRoles() { int user2RoleId2 = UserAdminService.getUserRole(userId2, roleId2, groupId); Assert.assertNotEquals(-1, user2RoleId2); + // get roles and verify + UserRequest getReq = new UserRequest(users); + UserRoleResponse res = null; + try { + res = service.getUserRoles(getReq); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + } + + List userInfoList = res.getUsers(); + Map> userRoles = getUserRoles(userInfoList); + for (var resRoles: userRoles.entrySet()) { + Assert.assertTrue("GetUserRoles did not include role", resRoles.getValue().contains(role1)); + Assert.assertTrue("GetUserRoles did not include role", resRoles.getValue().contains(role2)); + } + + // remove one role for both users UserRoleRequest req2 = new UserRoleRequest(users, List.of(role1)); try { - service.removeUserFromRoles(req2); + service.removeUserRoles(req2); } catch (Exception e) { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } - UserRequest getReq = new UserRequest(users); - UserRoleResponse res = null; + // get roles and verify try { res = service.getUserRoles(getReq); } catch (Exception e) { Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); } - List userInfoList = res.getUsers(); - Map> userRoles = getUserRoles(userInfoList); + userInfoList = res.getUsers(); + userRoles = getUserRoles(userInfoList); for (var resRoles: userRoles.entrySet()) { Assert.assertTrue("RemoveUserFromRole removed wrong role", resRoles.getValue().contains(role2)); Assert.assertFalse("RemoveUserFromRole did not remove role", resRoles.getValue().contains(role1)); } - // TODO: assert that result also has unassigned roles + // check that result also has unassigned roles + for (var userInfo: userInfoList) { + Assert.assertEquals(userInfo.getRoles().stream().map(UserRole::getName).collect(Collectors.toSet()), allRoles); + } // adjust cleanup removeRoleForUser(roleId1, userId1); removeRoleForUser(roleId1, userId2); + // remove one role for one user (left with one user that has one role) UserRoleRequest req3 = new UserRoleRequest(List.of(user2), List.of(role2)); try { - service.removeUserFromRoles(req3); + service.removeUserRoles(req3); } catch (Exception e) { Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); } + // get roles and verify UserRequest getReq2 = new UserRequest(users); try { res = service.getUserRoles(getReq2); @@ -322,28 +362,30 @@ public void testAddAndRemoveUser() { int operatorId = createAdminUser("test_admin3@study.org", userAdminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - UserAdminService.verifyOperatorGroup(operatorId, groupId); + UserAdminService.getOperatorAdminRoles(operatorId, groupId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); } String email = "testUser4@study.org"; - AddUserRequest req = new AddUserRequest("testUser4", email, null, null); + AddUserRequest req = new AddUserRequest(List.of(new AddUserRequest.User(email, "testUser4", null, null))); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { - service.createUser(req); + service.addUser(req); } catch (Exception e) { Assert.fail("Exception from UserAdminService.createUser: " + getStackTrace(e)); } int userId = -1; try { - userId = UserAdminService.getUserByEmail(email, -1); + userId = UserAdminService.verifyUserByEmail(email, -1); } catch (Exception e) { Assert.fail("Exception verifying UserAdminService.createUser: " + getStackTrace(e)); } + // at this point we have an admin and a user, and neither has roles + // add a role for the user, but let the admin service manage it via removeUser int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); @@ -353,14 +395,15 @@ public void testAddAndRemoveUser() { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } + UserRequest removeReq = new UserRequest(List.of(email)); try { - service.removeUser(req); + service.removeUser(removeReq); } catch (Exception e) { Assert.fail("Exception from UserAdminService.removeUser: " + getStackTrace(e)); } try { - UserAdminService.getUserByEmail(email, -1); + UserAdminService.verifyUserByEmail(email, -1); Assert.fail("UserAdminService.removeUser failed to remove user"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Invalid user")); @@ -398,9 +441,8 @@ private int createAdminUser(String email, int adminRoleId) { int userId = createTestUser(email, adminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - log.info("TEMP: createAdminUser: userId={}, adminRoleId={}", userId, adminRoleId); addUserRole(userId, adminRoleId, groupId); - log.info("Created admin user wih id {}", userId); + log.info("Created admin user {} wih id {} and adminRoleId={}", email, userId, adminRoleId); } catch (Exception e) { Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); } @@ -420,7 +462,7 @@ private void addAdminRoles(int adminUserId, int groupId, List roles) { private void setUserRoles(String email, List roles, String studyGroup) { try { - int userId = UserAdminService.getUserByEmail(email, -1); + int userId = UserAdminService.verifyUserByEmail(email, -1); int groupId = UserAdminService.verifyStudyGroup(studyGroup); for (String role : roles) { @@ -444,6 +486,23 @@ private Map> getUserRoles(List userInfoList) { return userRoles; } + private static List getAllRoles() { + return inTransaction(conn -> { + List roles = new ArrayList<>(); + try (PreparedStatement stmt = conn.prepareStatement("SELECT ar.role_id, ar.name FROM access_role ar")) { + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + // TODO: temp until we have display text + roles.add(rs.getString(2)); + } + } + } catch (SQLException e) { + throw new DsmInternalError("Error getting roles", e); + } + return roles; + }); + } + private static int createTestInstance(String instanceName, int studyGroupId) { int instanceId = createInstance(instanceName); SimpleResult res = inTransaction(conn -> { From a6dd55d7b045035098821cc6bfcfce98a85f7d3f Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 18 Jul 2023 15:29:55 -0400 Subject: [PATCH 24/35] Remove participant registration feature --- .../org/broadinstitute/dsm/DSMServer.java | 4 -- .../route/admin/RegisterParticipantRoute.java | 52 ------------------- 2 files changed, 56 deletions(-) delete mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java index 5f93682b6d..d49500010f 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/DSMServer.java @@ -123,7 +123,6 @@ import org.broadinstitute.dsm.route.ViewFilterRoute; import org.broadinstitute.dsm.route.admin.StudyRoleRoute; import org.broadinstitute.dsm.route.admin.UserRoleRoute; -import org.broadinstitute.dsm.route.admin.RegisterParticipantRoute; import org.broadinstitute.dsm.route.admin.UserRoute; import org.broadinstitute.dsm.route.dashboard.NewDashboardRoute; import org.broadinstitute.dsm.route.familymember.AddFamilyMemberRoute; @@ -925,9 +924,6 @@ private void setupMiscellaneousRoutes() { } private void setupAdminRoutes() { - RegisterParticipantRoute registerParticipantRoute = new RegisterParticipantRoute(); - post(uiRoot + RoutePath.REGISTER_PARTICIPANT, registerParticipantRoute, new JsonTransformer()); - StudyRoleRoute studyRoleRoute = new StudyRoleRoute(); get(uiRoot + RoutePath.STUDY_ROLE, studyRoleRoute, new JsonTransformer()); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java deleted file mode 100644 index 086039a71a..0000000000 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/RegisterParticipantRoute.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.broadinstitute.dsm.route.admin; - -import org.apache.commons.lang3.StringUtils; -import org.broadinstitute.dsm.model.Study; -import org.broadinstitute.dsm.model.defaultvalues.Defaultable; -import org.broadinstitute.dsm.model.defaultvalues.DefaultableMaker; -import org.broadinstitute.dsm.security.RequestHandler; -import org.broadinstitute.dsm.statics.RoutePath; -import org.broadinstitute.dsm.util.ParticipantUtil; -import org.broadinstitute.lddp.handlers.util.Result; -import spark.QueryParamsMap; -import spark.Request; -import spark.Response; - -public class RegisterParticipantRoute extends RequestHandler { - @Override - protected Object processRequest(Request request, Response response, String userId) throws Exception { - - QueryParamsMap queryParamsMap = request.queryMap(); - - String participantId = queryParamsMap.get(RoutePath.DDP_PARTICIPANT_ID).value(); - if (StringUtils.isBlank(participantId)) { - throw new IllegalArgumentException("participant ID cannot be empty"); - } - - String realm = queryParamsMap.get(RoutePath.REALM).value(); - if (StringUtils.isBlank(realm)) { - throw new IllegalArgumentException("realm cannot be empty"); - } - - // TODO: check user access - - if (!ParticipantUtil.isGuid(participantId)) { - throw new IllegalArgumentException("invalid participant ID"); - } - - try { - Study study = Study.of(realm.toUpperCase()); - Defaultable defaultable = DefaultableMaker.makeDefaultable(study); - boolean result = defaultable.generateDefaults(realm, participantId); - if (!result) { - // TODO: fix message and exception type - throw new IllegalArgumentException("invalid participant ID"); - } - return new Result(200); - } catch (Exception e) { - // TODO: fix status code - response.status(500); - return new Result(500, e.getMessage()); - } - } -} From b444c9fa9000dbbbeb868d1904dd08b294a63871 Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 18 Jul 2023 15:46:09 -0400 Subject: [PATCH 25/35] Initial implementation of DSM user administration --- .../src/main/java/org/broadinstitute/dsm/statics/RoutePath.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java index 74f7898aa9..699437d9a0 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/statics/RoutePath.java @@ -96,7 +96,6 @@ public class RoutePath { public static final String GET_DEFAULT_FILTERS = "getFiltersDefault"; public static final String GET_PARTICIPANT = "getParticipant"; public static final String GET_PARTICIPANT_DATA = "getParticipantData"; - public static final String REGISTER_PARTICIPANT = "registerParticipant"; public static final String USER = "admin/user"; public static final String USER_ROLE = "admin/userRole"; public static final String STUDY_ROLE = "admin/studyRole"; From be5eadc48101904784ad670d3f6547aec4590e43 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 20 Jul 2023 10:40:36 -0400 Subject: [PATCH 26/35] WIP --- .../dsm/service/admin/UpdateUserRequest.java | 33 +++++++++++++++ .../dsm/service/admin/UserAdminService.java | 42 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java new file mode 100644 index 0000000000..6cacc02935 --- /dev/null +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java @@ -0,0 +1,33 @@ +package org.broadinstitute.dsm.service.admin; + +import java.util.List; + +import lombok.Data; +import org.broadinstitute.dsm.db.dto.user.UserDto; + +@Data +public class UpdateUserRequest { + + private final List users; + + public UpdateUserRequest(List users) { + this.users = users; + } + + @Data + public static class User { + private final String email; + private final String name; + private final String phone; + + public User(String email, String name, String phone) { + this.email = email; + this.name = name; + this.phone = phone; + } + + public UserDto asUserDto() { + return new UserDto(-1, name, email, phone); + } + } +} diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index bc6afbd20a..2fb5205783 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -237,6 +237,7 @@ protected String validateEmailRequest(String email) { * Add user to study group. User request must include at least one role */ public void addUser(AddUserRequest req) { + int groupId = validateOperatorAdmin(); List users = req.getUsers(); if (CollectionUtils.isEmpty(users)) { throw new DSMBadRequestException("Invalid user list: blank"); @@ -250,7 +251,7 @@ public void addUser(AddUserRequest req) { if (StringUtils.isBlank(user.getName())) { throw new DSMBadRequestException("Invalid user name: blank"); } - if (getUserByEmail(email, -1) != null) { + if (getUserByEmail(email, groupId) != null) { throw new DsmInternalError("User already exists: " + email); } } @@ -264,10 +265,41 @@ public void addUser(AddUserRequest req) { } } + public void updateUser(UpdateUserRequest req) { + int groupId = validateOperatorAdmin(); + + List users = req.getUsers(); + if (CollectionUtils.isEmpty(users)) { + throw new DSMBadRequestException("Invalid user list: blank"); + } + + // pre-check to lessen likelihood of partial operation + for (var user: users) { + String email = validateEmailRequest(user.getEmail()); + + // not a strict requirement in DB, but now enforcing + if (StringUtils.isBlank(user.getName())) { + throw new DSMBadRequestException("Invalid user name: blank"); + } + if (getUserByEmail(email, groupId) == null) { + throw new DsmInternalError("User does not exist: " + email); + } + } + + UserDao userDao = new UserDao(); + for (var user: users) { + int userId = userDao.create(user.asUserDto()); + if (userId == -1) { + throw new DsmInternalError("Error creating user: " + user.getEmail()); + } + } + } + /** * Remove one or more users and their associated roles */ public void removeUser(UserRequest req) { + int groupId = validateOperatorAdmin(); if (CollectionUtils.isEmpty(req.getUsers())) { throw new DSMBadRequestException("Invalid user list: blank"); } @@ -276,7 +308,7 @@ public void removeUser(UserRequest req) { List userIds = new ArrayList<>(); for (String email: req.getUsers()) { validateEmailRequest(email); - userIds.add(verifyUserByEmail(email, -1)); + userIds.add(verifyUserByEmail(email, groupId)); } UserDao userDao = new UserDao(); @@ -286,6 +318,12 @@ public void removeUser(UserRequest req) { } } + protected int validateOperatorAdmin() { + int groupId = verifyStudyGroup(studyGroup); + getAdminRoles(groupId); + return groupId; + } + /** * Return user information and user roles, both assigned and unassigned roles for study * From 6119f4ba119d2bef6f35e7639193a481c3a53817 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 20 Jul 2023 12:11:52 -0400 Subject: [PATCH 27/35] WIP --- .../org/broadinstitute/dsm/service/admin/UserAdminService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 2fb5205783..0921aef9d7 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -267,7 +267,6 @@ public void addUser(AddUserRequest req) { public void updateUser(UpdateUserRequest req) { int groupId = validateOperatorAdmin(); - List users = req.getUsers(); if (CollectionUtils.isEmpty(users)) { throw new DSMBadRequestException("Invalid user list: blank"); @@ -320,6 +319,7 @@ public void removeUser(UserRequest req) { protected int validateOperatorAdmin() { int groupId = verifyStudyGroup(studyGroup); + // will throw if operator is not user admin getAdminRoles(groupId); return groupId; } From a164c6cc5d9a90ef13f023c4ba13878e0da01d6d Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 20 Jul 2023 13:32:23 -0400 Subject: [PATCH 28/35] Updates due to code review, mostly reworked UserDao --- .../dsm/db/dao/user/UserDao.java | 67 +++++++++---------- .../dsm/db/dto/user/UserDto.java | 6 +- .../dsm/service/admin/AddUserRequest.java | 2 +- .../dsm/service/admin/UserAdminService.java | 12 +--- .../dsm/service/admin/UserInfo.java | 4 -- .../service/admin/UserAdminServiceTest.java | 18 +++++ .../broadinstitute/dsm/util/DBTestUtil.java | 2 +- 7 files changed, 58 insertions(+), 53 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index c8974aa645..33f3734775 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -5,13 +5,16 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.Optional; import lombok.NonNull; import org.broadinstitute.dsm.db.dao.Dao; +import org.broadinstitute.dsm.db.dao.util.DaoUtil; import org.broadinstitute.dsm.db.dto.user.UserDto; import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.lddp.db.SimpleResult; +import spark.utils.StringUtils; public class UserDao implements Dao { @@ -19,12 +22,15 @@ public class UserDao implements Dao { public static final String NAME = "name"; public static final String EMAIL = "email"; public static final String PHONE_NUMBER = "phone_number"; + public static final String IS_ACTIVE = "is_active"; private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, phone_number, is_active) VALUES (?,?,?,?)"; private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM access_user WHERE user_id = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = - "SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.email = ?"; + "SELECT user.user_id, user.name, user.email, user.phone_number, user.is_active FROM access_user user " + + "WHERE user.email = ?"; private static final String SQL_SELECT_USER_BY_ID = - "SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.user_id = ?"; + "SELECT user.user_id, user.name, user.email, user.phone_number, user.is_active FROM access_user user " + + "WHERE user.user_id = ?"; public Optional getUserByEmail(@NonNull String email) { SimpleResult results = inTransaction((conn) -> { @@ -36,7 +42,8 @@ public Optional getUserByEmail(@NonNull String email) { dbVals.resultValue = new UserDto(rs.getInt(USER_ID), rs.getString(NAME), rs.getString(EMAIL), - rs.getString(PHONE_NUMBER)); + rs.getString(PHONE_NUMBER), + rs.getInt(IS_ACTIVE)); } } } catch (SQLException ex) { @@ -46,7 +53,7 @@ public Optional getUserByEmail(@NonNull String email) { }); if (results.resultException != null) { - throw new RuntimeException("Error getting list of realms ", results.resultException); + throw new DsmInternalError("Error getting user by email " + email, results.resultException); } return Optional.ofNullable((UserDto) results.resultValue); } @@ -62,7 +69,8 @@ public Optional get(long userId) { dbVals.resultValue = new UserDto(rs.getInt(USER_ID), rs.getString(NAME), rs.getString(EMAIL), - rs.getString(PHONE_NUMBER)); + rs.getString(PHONE_NUMBER), + rs.getInt(IS_ACTIVE)); } } } catch (SQLException ex) { @@ -72,52 +80,43 @@ public Optional get(long userId) { }); if (results.resultException != null) { - throw new RuntimeException("Error getting list of realms ", results.resultException); + throw new DsmInternalError("Error getting user by id " + userId, results.resultException); } return Optional.ofNullable((UserDto) results.resultValue); } @Override public int create(UserDto userDto) { - SimpleResult results = inTransaction((conn) -> { - SimpleResult execResult = new SimpleResult(-1); - try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, PreparedStatement.RETURN_GENERATED_KEYS)) { - stmt.setString(1, userDto.getName().orElse("")); - stmt.setString(2, userDto.getEmail().orElse("")); - stmt.setString(3, userDto.getPhoneNumber().orElse("")); - stmt.setInt(4, userDto.getIsActive().orElse(0)); + String email = userDto.getEmail().orElse(null); + if (StringUtils.isBlank(email)) { + throw new DsmInternalError("Error inserting user: email is blank"); + } + return inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, Statement.RETURN_GENERATED_KEYS)) { + stmt.setString(1, userDto.getName().orElse(null)); + stmt.setString(2, email); + stmt.setString(3, userDto.getPhoneNumber().orElse(null)); + stmt.setInt(4, userDto.getIsActive().orElse(1)); stmt.executeUpdate(); try (ResultSet rs = stmt.getGeneratedKeys()) { - if (rs.next()) { - execResult.resultValue = rs.getInt(1); + if (!rs.next()) { + throw new DsmInternalError("Error inserting user " + email); } + return rs.getInt(1); } } catch (SQLException ex) { - execResult.resultException = ex; + throw new DsmInternalError("Error inserting user " + email, ex); } - return execResult; }); - if (results.resultException != null) { - throw new DsmInternalError("Error inserting user " + userDto.getEmail(), results.resultException); - } - return (int) results.resultValue; } @Override public int delete(int id) { - SimpleResult results = inTransaction((conn) -> { - SimpleResult execResult = new SimpleResult(); - try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_BY_ID)) { - stmt.setInt(1, id); - execResult.resultValue = stmt.executeUpdate(); - } catch (SQLException ex) { - execResult.resultException = ex; - } - return execResult; - }); - if (results.resultException != null) { - throw new DsmInternalError("Error deleting user with ID " + id, results.resultException); + SimpleResult simpleResult = DaoUtil.deleteById(id, SQL_DELETE_USER_BY_ID); + if (simpleResult.resultException != null) { + throw new DsmInternalError("Error deleting user with ID: " + id, + simpleResult.resultException); } - return (int) results.resultValue; + return (int) simpleResult.resultValue; } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java index e3e4a397dc..30d8a67340 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java @@ -2,9 +2,11 @@ import java.util.Optional; +import lombok.AllArgsConstructor; import lombok.Setter; @Setter +@AllArgsConstructor public class UserDto { private int id; @@ -13,15 +15,13 @@ public class UserDto { private String phoneNumber; private Integer isActive; - public UserDto(int id, String name, String email, String phoneNumber) { - this.id = id; + public UserDto(String name, String email, String phoneNumber) { this.name = name; this.email = email; this.phoneNumber = phoneNumber; } public UserDto() { - } public int getId() { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java index f4174f937e..1290eedaef 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java @@ -29,7 +29,7 @@ public User(String email, String name, String phone, List roles) { } public UserDto asUserDto() { - return new UserDto(-1, name, email, phone); + return new UserDto(name, email, phone); } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index bc6afbd20a..b70e873563 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.stream.Collectors; -import liquibase.pro.packaged.S; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; @@ -257,10 +256,7 @@ public void addUser(AddUserRequest req) { UserDao userDao = new UserDao(); for (var user: users) { - int userId = userDao.create(user.asUserDto()); - if (userId == -1) { - throw new DsmInternalError("Error creating user: " + user.getEmail()); - } + userDao.create(user.asUserDto()); } } @@ -310,7 +306,7 @@ public UserRoleResponse getUserRoles(UserRequest req) { } List userRoles = convertToUserRoles(roles, studyRoles); UserInfo userInfo = entry.getValue(); - userInfo.addRoles(userRoles); + userInfo.setRoles(userRoles); res.addUser(userInfo); } @@ -526,10 +522,6 @@ protected static Map getRolesForAdmin(int groupId, String admi }); } - protected static void setAdminForRole(int roleId, int adminRoleId, int groupId) { - - } - protected static Map> getRolesForStudyUsers(int groupId) { return inTransaction(conn -> { Map> roles = new HashMap<>(); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java index 9aa11c64f7..b74e7032a0 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java @@ -25,8 +25,4 @@ public UserInfo(String email, String name, String phone) { this.phone = phone; this.roles = new ArrayList<>(); } - - public void addRoles(List roles) { - this.roles = roles; - } } diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 09b49da320..6bb6a7c004 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -203,6 +204,23 @@ public void testGetUserByEmailAndGroup() { } catch (Exception e) { Assert.fail("Exception from UserAdminService.getUserByEmail: " + getStackTrace(e)); } + + // we don't have coverage for this elsewhere + UserDao userDao = new UserDao(); + try { + Optional res = userDao.getUserByEmail(email); + Assert.assertTrue(res.isPresent()); + Assert.assertEquals(userId, res.get().getId()); + } catch (Exception e) { + Assert.fail("Exception from UserDao.getUserByEmail: " + getStackTrace(e)); + } + try { + Optional res = userDao.get(userId); + Assert.assertTrue(res.isPresent()); + Assert.assertEquals(userId, res.get().getId()); + } catch (Exception e) { + Assert.fail("Exception from UserDao.get: " + getStackTrace(e)); + } } @Test diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/util/DBTestUtil.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/util/DBTestUtil.java index ee9ff63ad0..0e26e1d828 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/util/DBTestUtil.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/util/DBTestUtil.java @@ -1139,7 +1139,7 @@ public static DDPInstanceDto createTestDdpInstance(DDPInstanceDao ddpInstanceDao } public static UserDto createTestDsmUser(String name, String email, UserDao userDao, UserDto userDto) { - userDto = new UserDto(0, name, email, ""); + userDto = new UserDto(name, email, ""); userDto.setId(userDao.create(userDto)); return userDto; } From ee54a07cac8c8e162e57ecfa8a1f8df5edc877dc Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 21 Jul 2023 11:19:18 -0400 Subject: [PATCH 29/35] WIP --- .../dsm/service/admin/UserAdminService.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 0921aef9d7..c4c7edb587 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -20,6 +20,7 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.broadinstitute.ddp.db.TransactionWrapper; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; @@ -93,6 +94,9 @@ public class UserAdminService { private static final String SQL_DELETE_USER_ROLE = "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ? AND group_id = ?"; + private static final String SQL_UPDATE_USER = + "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; + private static final String SQL_INSERT_GROUP = "INSERT INTO ddp_group SET name = ?"; @@ -711,6 +715,29 @@ protected static int addUserRole(int userId, int roleId, int groupId) { }); } + public static int updateUser(int userId, String name, String phone) { + return inTransaction(conn -> { + int id = -1; + try (PreparedStatement stmt = conn.prepareStatement(SQL_UPDATE_USER)) { + stmt.setString(1, name); + stmt.setString(2, phone); + stmt.setInt(3, userId); + int result = stmt.executeUpdate(); + if (result != 1) { + throw new DsmInternalError("Error updating user. Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + id = rs.getInt(1); + } + } + } catch (SQLException ex) { + throw new DsmInternalError("Error adding user to role", ex); + } + return id; + }); + } + protected static int getGroupRole(int groupId, int roleId) { return inTransaction(conn -> { int id = -1; From a038961717d2483a39b2da0e242ad13de233e90d Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 21 Jul 2023 17:32:22 -0400 Subject: [PATCH 30/35] WIP --- .../dsm/db/dao/user/UserDao.java | 22 ++ .../dsm/route/admin/UserRoute.java | 15 + .../dsm/service/admin/UpdateUserRequest.java | 2 +- .../dsm/service/admin/UserAdminService.java | 76 +++-- .../service/admin/UserAdminServiceTest.java | 283 ++++++++++++------ 5 files changed, 263 insertions(+), 135 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index 33f3734775..5cf21049b6 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -24,6 +24,8 @@ public class UserDao implements Dao { public static final String PHONE_NUMBER = "phone_number"; public static final String IS_ACTIVE = "is_active"; private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, phone_number, is_active) VALUES (?,?,?,?)"; + private static final String SQL_UPDATE_USER = + "UPDATE access_user SET name = ?, phone_number = ?, is_active = ? WHERE user_id = ?"; private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM access_user WHERE user_id = ?"; private static final String SQL_SELECT_USER_BY_EMAIL = "SELECT user.user_id, user.name, user.email, user.phone_number, user.is_active FROM access_user user " @@ -110,6 +112,26 @@ public int create(UserDto userDto) { }); } + public static void update(int userId, UserDto userDto) { + String email = userDto.getEmail().orElseThrow(() -> new DsmInternalError("Error updating user: missing email")); + String errorMsg = "Error updating user " + email; + int res = inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_UPDATE_USER)) { + stmt.setString(1, userDto.getName().orElse(null)); + stmt.setString(2, userDto.getPhoneNumber().orElse(null)); + stmt.setInt(3, userDto.getIsActive().orElse(1)); + stmt.setInt(4, userId); + int result = stmt.executeUpdate(); + if (result != 1) { + throw new DsmInternalError(errorMsg + " Result count was " + result); + } + return result; + } catch (SQLException ex) { + throw new DsmInternalError(errorMsg, ex); + } + }); + } + @Override public int delete(int id) { SimpleResult simpleResult = DaoUtil.deleteById(id, SQL_DELETE_USER_BY_ID); diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java index fcb49e3906..0c3bbe2a4b 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/admin/UserRoute.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.broadinstitute.dsm.security.RequestHandler; import org.broadinstitute.dsm.service.admin.AddUserRequest; +import org.broadinstitute.dsm.service.admin.UpdateUserRequest; import org.broadinstitute.dsm.service.admin.UserAdminService; import org.broadinstitute.dsm.service.admin.UserRequest; import org.broadinstitute.dsm.statics.RoutePath; @@ -47,6 +48,20 @@ public Object processRequest(Request request, Response response, String userId) } catch (Exception e) { return UserRoleRoute.handleError(e, "adding user", response); } + } else if (requestMethod.equals(RoutePath.RequestMethod.PUT.toString())) { + UpdateUserRequest req; + try { + req = new Gson().fromJson(body, UpdateUserRequest.class); + } catch (Exception e) { + log.info("Invalid request format for {}", body); + response.status(400); + return "Invalid request format"; + } + try { + adminService.updateUser(req); + } catch (Exception e) { + return UserRoleRoute.handleError(e, "updating user", response); + } } else if (requestMethod.equals(RoutePath.RequestMethod.DELETE.toString())) { UserRequest req; try { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java index 6cacc02935..aad675e7eb 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UpdateUserRequest.java @@ -27,7 +27,7 @@ public User(String email, String name, String phone) { } public UserDto asUserDto() { - return new UserDto(-1, name, email, phone); + return new UserDto(name, email, phone); } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 09e2be0239..37eedfbe0b 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -19,7 +19,6 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.broadinstitute.ddp.db.TransactionWrapper; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; @@ -93,9 +92,6 @@ public class UserAdminService { private static final String SQL_DELETE_USER_ROLE = "DELETE FROM access_user_role_group WHERE user_id = ? AND role_id = ? AND group_id = ?"; - private static final String SQL_UPDATE_USER = - "INSERT INTO access_user_role_group SET user_id = ?, role_id = ?, group_id = ?"; - private static final String SQL_INSERT_GROUP = "INSERT INTO ddp_group SET name = ?"; @@ -142,6 +138,7 @@ public void addUserRoles(UserRoleRequest req) { List roleNames = req.getRoles(); validateRoles(roleNames, studyRoles.keySet()); + // TODO: validate all user emails first for (String userEmail: req.getUsers()) { if (StringUtils.isBlank(userEmail)) { throw new DSMBadRequestException("Invalid user email: blank"); @@ -150,6 +147,11 @@ public void addUserRoles(UserRoleRequest req) { } } + /** + * Add roles for user + * + * @param roles list of proposed roles, already validated + */ protected void addRoles(String email, List roles, int groupId, Map studyRoles) { int userId = verifyUserByEmail(email, groupId); @@ -189,11 +191,20 @@ public void removeUserRoles(UserRoleRequest req) { } } + /** + * Remove roles for user + * + * @param roles list of proposed roles, already validated + */ protected void removeUserRoles(String email, List roles, int groupId, Map studyRoles) { - // TODO loop is similar to addUserRoles -DC + // TODO: move this to caller in a separate loop int userId = verifyUserByEmail(email, groupId); + List existingRoles = getRolesForUser(userId, groupId); + if (CollectionUtils.subtract(existingRoles, roles).isEmpty()) { + throw new DSMBadRequestException("Cannot remove all roles for user " + email); + } - for (String role : roles) { +; for (String role : roles) { if (StringUtils.isBlank(role)) { throw new DsmInternalError("Invalid role: blank"); } @@ -221,12 +232,16 @@ protected void validateRoleRequest(UserRoleRequest req) { } protected void validateRoles(List roleNames, Set validRoleNames) { + String msg = String.format("Invalid roles for study group %s: ", studyGroup); + if (CollectionUtils.isEmpty(roleNames)) { + throw new DSMBadRequestException(msg + "None provided"); + } if (validRoleNames.containsAll(roleNames)) { return; } - Collection badRoles = CollectionUtils.subtract(roleNames, validRoleNames); - String msg = String.format("Invalid roles for study group %s: %s", studyGroup, String.join(",", badRoles)); - throw new DSMBadRequestException(msg); + Collection badRoles = CollectionUtils.subtract(roleNames, validRoleNames).stream() + .map(r -> r.isEmpty() ? "" : r).collect(Collectors.toSet()); + throw new DSMBadRequestException(msg + String.join(",", badRoles)); } protected String validateEmailRequest(String email) { @@ -240,7 +255,9 @@ protected String validateEmailRequest(String email) { * Add user to study group. User request must include at least one role */ public void addUser(AddUserRequest req) { - int groupId = validateOperatorAdmin(); + int groupId = verifyStudyGroup(studyGroup); + Map studyRoles = getAdminRoles(groupId); + List users = req.getUsers(); if (CollectionUtils.isEmpty(users)) { throw new DSMBadRequestException("Invalid user list: blank"); @@ -257,11 +274,14 @@ public void addUser(AddUserRequest req) { if (getUserByEmail(email, groupId) != null) { throw new DsmInternalError("User already exists: " + email); } + + validateRoles(user.getRoles(), studyRoles.keySet()); } UserDao userDao = new UserDao(); for (var user: users) { userDao.create(user.asUserDto()); + addRoles(user.getEmail(), user.getRoles(), groupId, studyRoles); } } @@ -272,6 +292,7 @@ public void updateUser(UpdateUserRequest req) { throw new DSMBadRequestException("Invalid user list: blank"); } + Map usersById = new HashMap<>(); // pre-check to lessen likelihood of partial operation for (var user: users) { String email = validateEmailRequest(user.getEmail()); @@ -280,17 +301,15 @@ public void updateUser(UpdateUserRequest req) { if (StringUtils.isBlank(user.getName())) { throw new DSMBadRequestException("Invalid user name: blank"); } - if (getUserByEmail(email, groupId) == null) { + StudyUser studyUser = getUserByEmail(email, groupId); + if (studyUser == null) { throw new DsmInternalError("User does not exist: " + email); } + usersById.put(studyUser.getId(), user); } - UserDao userDao = new UserDao(); - for (var user: users) { - int userId = userDao.create(user.asUserDto()); - if (userId == -1) { - throw new DsmInternalError("Error creating user: " + user.getEmail()); - } + for (var entry: usersById.entrySet()) { + UserDao.update(entry.getKey(), entry.getValue().asUserDto()); } } @@ -707,29 +726,6 @@ protected static int addUserRole(int userId, int roleId, int groupId) { }); } - public static int updateUser(int userId, String name, String phone) { - return inTransaction(conn -> { - int id = -1; - try (PreparedStatement stmt = conn.prepareStatement(SQL_UPDATE_USER)) { - stmt.setString(1, name); - stmt.setString(2, phone); - stmt.setInt(3, userId); - int result = stmt.executeUpdate(); - if (result != 1) { - throw new DsmInternalError("Error updating user. Result count was " + result); - } - try (ResultSet rs = stmt.getGeneratedKeys()) { - if (rs.next()) { - id = rs.getInt(1); - } - } - } catch (SQLException ex) { - throw new DsmInternalError("Error adding user to role", ex); - } - return id; - }); - } - protected static int getGroupRole(int groupId, int roleId) { return inTransaction(conn -> { int id = -1; diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index 6bb6a7c004..d342c13ea2 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,12 +19,14 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.broadinstitute.dsm.DbTxnBaseTest; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.db.dto.user.UserDto; import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.statics.RoutePath; import org.broadinstitute.lddp.db.SimpleResult; +import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -65,10 +68,6 @@ public static void setup() { @AfterClass public static void tearDown() { - for (int groupRoleId: createdGroupRoles) { - UserAdminService.deleteGroupRole(groupRoleId); - } - UserDao userDao = new UserDao(); for (var entry: createdUserRoles.entrySet()) { int userId = entry.getKey(); @@ -82,6 +81,13 @@ public static void tearDown() { UserAdminService.deleteStudyGroup(studyGroupId); } + @After + public void cleanup() { + for (int groupRoleId: createdGroupRoles) { + UserAdminService.deleteGroupRole(groupRoleId); + } + } + private int createTestUser(String email, int roleId) { int userId = createUser(email); List roleIds = new ArrayList<>(); @@ -224,37 +230,59 @@ public void testGetUserByEmailAndGroup() { } @Test - public void testUserRoles() { - int operatorId = createAdminUser("test_admin2@study.org", userAdminRoleId); - int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + public void testValidateRoles() { + UserAdminService service = new UserAdminService("not needed for test", TEST_GROUP); + + Set studyRoles = Set.of("A", "B", "C"); try { - Map adminRoles = UserAdminService.getOperatorAdminRoles(operatorId, groupId); - Assert.assertTrue(adminRoles == null || adminRoles.isEmpty()); + service.validateRoles(new ArrayList<>(), studyRoles); + Assert.fail("UserAdminService.validateRoles should fail with no roles"); } catch (Exception e) { - Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); + Assert.assertTrue(e.getMessage().contains("Invalid roles for study group")); + Assert.assertTrue(e.getMessage().contains("None provided")); + } + + try { + service.validateRoles(List.of("A", "", "B"), studyRoles); + Assert.fail("UserAdminService.validateRoles should fail with blank role"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid roles for study group")); + Assert.assertTrue(e.getMessage().contains("")); + } + + try { + service.validateRoles(List.of("A", "XXX", "B"), studyRoles); + Assert.fail("UserAdminService.validateRoles should fail with invalid role for study"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid roles for study group")); + Assert.assertTrue(e.getMessage().contains("XXX")); } + try { + service.validateRoles(List.of("A", "B"), studyRoles); + } catch (Exception e) { + Assert.fail("UserAdminService.validateRoles should not fail with valid roles for study"); + } + } + + @Test + public void testUserRoles() { String role1 = "upload_onc_history"; - int roleId1 = UserAdminService.getRoleId(role1); - Assert.assertTrue(roleId1 > 0); String role2 = "upload_ror_file"; - int roleId2 = UserAdminService.getRoleId(role2); - Assert.assertTrue(roleId2 > 0); List roles = List.of(role1, role2); - + Map rolesToId = getRoleIds(roles); String user1 = "testUser2@study.org"; - int userId1 = createTestUser(user1, roleId1); - addRoleForUser(roleId2, userId1); String user2 = "testUser3@study.org"; - int userId2 = createTestUser(user2, roleId1); - addRoleForUser(roleId2, userId2); + List users = List.of(user1, user2); + Map usersToId = setupUsers(users, rolesToId.values()); + + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); // let operator manage one of the roles - addGroupRole(roleId1, userAdminRoleId); + int operatorId = setupAdmin("test_admin2@study.org", List.of(rolesToId.get(role1)), groupId); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); - List users = List.of(user1, user2); UserRoleRequest req = new UserRoleRequest(users, roles); try { service.addUserRoles(req); @@ -264,7 +292,7 @@ public void testUserRoles() { } // both roles are now in the study - addGroupRole(roleId2, userAdminRoleId); + addGroupRole(rolesToId.get(role2), userAdminRoleId); // verify by getting all study roles Set allRoles = Set.of(role1, role2); @@ -278,15 +306,11 @@ public void testUserRoles() { } // verify internally - int userRoleId = UserAdminService.getUserRole(userId1, roleId1, groupId); - Assert.assertNotEquals(-1, userRoleId); - int userRoleId2 = UserAdminService.getUserRole(userId1, roleId2, groupId); - Assert.assertNotEquals(-1, userRoleId2); - - int user2RoleId = UserAdminService.getUserRole(userId2, roleId1, groupId); - Assert.assertNotEquals(-1, user2RoleId); - int user2RoleId2 = UserAdminService.getUserRole(userId2, roleId2, groupId); - Assert.assertNotEquals(-1, user2RoleId2); + for (var userId: usersToId.values()) { + for (var roleId: rolesToId.values()) { + verifyUserHasRole(userId, roleId, groupId); + } + } // get roles and verify UserRequest getReq = new UserRequest(users); @@ -298,11 +322,9 @@ public void testUserRoles() { } List userInfoList = res.getUsers(); - Map> userRoles = getUserRoles(userInfoList); - for (var resRoles: userRoles.entrySet()) { - Assert.assertTrue("GetUserRoles did not include role", resRoles.getValue().contains(role1)); - Assert.assertTrue("GetUserRoles did not include role", resRoles.getValue().contains(role2)); - } + Map> userRoles = getUserRoles(userInfoList); + verifyResponseRoles(userRoles.get(user1), List.of(role1, role2), List.of(role1, role2)); + verifyResponseRoles(userRoles.get(user2), List.of(role1, role2), List.of(role1, role2)); // remove one role for both users UserRoleRequest req2 = new UserRoleRequest(users, List.of(role1)); @@ -321,10 +343,8 @@ public void testUserRoles() { userInfoList = res.getUsers(); userRoles = getUserRoles(userInfoList); - for (var resRoles: userRoles.entrySet()) { - Assert.assertTrue("RemoveUserFromRole removed wrong role", resRoles.getValue().contains(role2)); - Assert.assertFalse("RemoveUserFromRole did not remove role", resRoles.getValue().contains(role1)); - } + verifyResponseRoles(userRoles.get(user1), List.of(role1, role2), List.of(role2)); + verifyResponseRoles(userRoles.get(user2), List.of(role1, role2), List.of(role2)); // check that result also has unassigned roles for (var userInfo: userInfoList) { @@ -332,61 +352,51 @@ public void testUserRoles() { } // adjust cleanup - removeRoleForUser(roleId1, userId1); - removeRoleForUser(roleId1, userId2); + removeRoleForUser(rolesToId.get(role1), usersToId.get(user1)); + removeRoleForUser(rolesToId.get(role1), usersToId.get(user2)); - // remove one role for one user (left with one user that has one role) + // remove last role for one user, which should not be allowed UserRoleRequest req3 = new UserRoleRequest(List.of(user2), List.of(role2)); try { service.removeUserRoles(req3); + Assert.fail("UserAdminService.removeUserRoles should fail to remove all user roles"); } catch (Exception e) { - Assert.fail("Exception from UserAdminService.removeUserFromRoles: " + getStackTrace(e)); - } - - // get roles and verify - UserRequest getReq2 = new UserRequest(users); - try { - res = service.getUserRoles(getReq2); - } catch (Exception e) { - Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + Assert.assertTrue(e.getMessage().contains("Cannot remove all roles for user")); } + } - userInfoList = res.getUsers(); - for (var userInfo: userInfoList) { - List resRoles = userInfo.getRoles(); - if (userInfo.getEmail().equals(user1)) { - Assert.assertEquals(2, resRoles.size()); - - List hasRole = resRoles.stream().filter(UserRole::isHasRole).collect(Collectors.toList()); - Assert.assertEquals(1, hasRole.size()); + private static void verifyUserHasRole(int userId, int roleId, int groupId) { + int userRoleId = UserAdminService.getUserRole(userId, roleId, groupId); + Assert.assertNotEquals(-1, userRoleId); + } - List filteredRoles = resRoles.stream().filter(r -> r.getName().equals(role2)).collect(Collectors.toList()); - Assert.assertEquals(1, filteredRoles.size()); - Assert.assertTrue(filteredRoles.get(0).isHasRole()); - } else { - Assert.assertEquals(2, resRoles.size()); + private static void verifyResponseRoles(Map userRoles, List allRoles, List assignedRoles) { + Assert.assertTrue(allRoles.containsAll(assignedRoles)); - List hasRole = resRoles.stream().filter(UserRole::isHasRole).collect(Collectors.toList()); - Assert.assertTrue("RemoveUserFromRole did not remove role", hasRole.isEmpty()); - } + for (String role: assignedRoles) { + Boolean isAssigned = userRoles.get(role); + Assert.assertNotNull("GetUserRoles did not include role", isAssigned); + Assert.assertEquals(assignedRoles.contains(role), isAssigned); } - - // adjust cleanup - removeRoleForUser(roleId2, userId2); } @Test public void testAddAndRemoveUser() { - int operatorId = createAdminUser("test_admin3@study.org", userAdminRoleId); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); - try { - UserAdminService.getOperatorAdminRoles(operatorId, groupId); - } catch (Exception e) { - Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); - } - String email = "testUser4@study.org"; - AddUserRequest req = new AddUserRequest(List.of(new AddUserRequest.User(email, "testUser4", null, null))); + String role1 = "upload_onc_history"; + String role2 = "upload_ror_file"; + List roles = List.of(role1, role2); + Map rolesToId = getRoleIds(roles); + + // include a role for the user, but let the admin service manage it via removeUser + int operatorId = setupAdmin("test_admin3@study.org", List.of(rolesToId.get(role1)), groupId); + addGroupRole(rolesToId.get(role2), userAdminRoleId); + + String user = "testUser4@study.org"; + String userName = "testUser4"; + AddUserRequest req = new AddUserRequest(List.of(new AddUserRequest.User(user, userName, null, + List.of(role1)))); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { @@ -395,25 +405,53 @@ public void testAddAndRemoveUser() { Assert.fail("Exception from UserAdminService.createUser: " + getStackTrace(e)); } - int userId = -1; + // add a role + UserRoleRequest roleReq = new UserRoleRequest(List.of(user), List.of(role2)); try { - userId = UserAdminService.verifyUserByEmail(email, -1); + service.addUserRoles(roleReq); } catch (Exception e) { - Assert.fail("Exception verifying UserAdminService.createUser: " + getStackTrace(e)); + Assert.fail("Exception from UserAdminService.addUserRoles: " + getStackTrace(e)); } - // at this point we have an admin and a user, and neither has roles - // add a role for the user, but let the admin service manage it via removeUser - int roleId = UserAdminService.getRoleId("upload_onc_history"); - Assert.assertTrue(roleId > 0); + // verify user info + UserRoleResponse res = null; + try { + res = service.getUserRoles(null); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + } + + List userInfoList = res.getUsers(); + verifyUserInfo(userInfoList, user, userName, null); + + Map> userRoles = getUserRoles(userInfoList); + verifyResponseRoles(userRoles.get(user), List.of(role1), List.of(role1)); + // update user + String newUserName = "newName"; + String phone = "555-1212"; + UpdateUserRequest.User updateUser = new UpdateUserRequest.User(user, newUserName, phone); + UpdateUserRequest updateReq = new UpdateUserRequest(List.of(updateUser)); try { - UserAdminService.addUserRole(userId, roleId, groupId); + service.updateUser(updateReq); } catch (Exception e) { - Assert.fail("Exception from UserAdminService.addUserRole: " + getStackTrace(e)); + Assert.fail("Exception from UserAdminService.updateUser: " + getStackTrace(e)); } - UserRequest removeReq = new UserRequest(List.of(email)); + // verify user info again + try { + res = service.getUserRoles(null); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); + } + + userInfoList = res.getUsers(); + verifyUserInfo(userInfoList, user, newUserName, phone); + + userRoles = getUserRoles(userInfoList); + verifyResponseRoles(userRoles.get(user), List.of(role1, role2), List.of(role1, role2)); + + UserRequest removeReq = new UserRequest(List.of(user)); try { service.removeUser(removeReq); } catch (Exception e) { @@ -421,13 +459,28 @@ public void testAddAndRemoveUser() { } try { - UserAdminService.verifyUserByEmail(email, -1); + UserAdminService.verifyUserByEmail(user, -1); Assert.fail("UserAdminService.removeUser failed to remove user"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Invalid user")); } } + private static void verifyUserInfo(List userInfoList, String email, String name, String phone) { + UserInfo userInfo = null; + for (UserInfo u: userInfoList) { + if (u.getEmail().equals(email)) { + userInfo = u; + break; + } + } + Assert.assertNotNull(userInfo); + + Assert.assertEquals(email, userInfo.getEmail()); + Assert.assertEquals(name, userInfo.getName()); + Assert.assertEquals(phone, userInfo.getPhone()); + } + @Test public void testGetStudyGroup() { Map qp = new HashMap<>(); @@ -467,6 +520,48 @@ private int createAdminUser(String email, int adminRoleId) { return userId; } + private int setupAdmin(String adminEmail, List rolesToManage, int groupId) { + int operatorId = createAdminUser(adminEmail, userAdminRoleId); + try { + Map adminRoles = UserAdminService.getOperatorAdminRoles(operatorId, groupId); + Assert.assertTrue("adminRoles = " + adminRoles.keySet(), adminRoles.isEmpty()); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.verifyOperatorForGroup: " + getStackTrace(e)); + } + + if (rolesToManage != null) { + for (int roleId: rolesToManage) { + addGroupRole(roleId, userAdminRoleId); + } + } + return operatorId; + } + + private Map setupUsers(List users, Collection roleIds) { + + Map userToId = new HashMap<>(); + for (String user: users) { + int userId = createTestUser(user, -1); + for (int roleId : roleIds) { + addRoleForUser(roleId, userId); + } + userToId.put(user, userId); + } + return userToId; + } + + + private Map getRoleIds(List roles) { + + Map roleToId = new HashMap<>(); + for (String role: roles) { + int roleId = UserAdminService.getRoleId(role); + Assert.assertTrue(roleId > 0); + roleToId.put(role, roleId); + } + return roleToId; + } + private void addAdminRoles(int adminUserId, int groupId, List roles) { try { for (String role : roles) { @@ -494,11 +589,11 @@ private void setUserRoles(String email, List roles, String studyGroup) { } } - private Map> getUserRoles(List userInfoList) { - Map> userRoles = new HashMap<>(); + private static Map> getUserRoles(List userInfoList) { + Map> userRoles = new HashMap<>(); for (UserInfo userInfo: userInfoList) { - List roles = userInfo.getRoles().stream().filter(UserRole::isHasRole) - .map(UserRole::getName).collect(Collectors.toList()); + Map roles = userInfo.getRoles().stream() + .collect(Collectors.toMap(UserRole::getName, UserRole::isHasRole)); userRoles.put(userInfo.getEmail(), roles); } return userRoles; From 5e9cecdded3e94fcd6950a292114cca89e9da672 Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 24 Jul 2023 16:42:36 -0400 Subject: [PATCH 31/35] WIP --- .../dsm/service/admin/UserAdminService.java | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 37eedfbe0b..a3cc3d2ed7 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -138,12 +138,17 @@ public void addUserRoles(UserRoleRequest req) { List roleNames = req.getRoles(); validateRoles(roleNames, studyRoles.keySet()); - // TODO: validate all user emails first - for (String userEmail: req.getUsers()) { - if (StringUtils.isBlank(userEmail)) { - throw new DSMBadRequestException("Invalid user email: blank"); - } - addRoles(userEmail, roleNames, groupId, studyRoles); + // pre-check to lessen likelihood of partial operation + Map userIds = new HashMap<>(); + for (String email: req.getUsers()) { + int userId = verifyUserByEmail(email, groupId); + userIds.put(email, userId); + } + + for (var entry: userIds.entrySet()) { + addRoles(entry.getValue(), roleNames, groupId, studyRoles); + log.info("Removed roles for user {} in study group {}: {}", entry.getKey(), studyGroup, + String.join(",", roleNames)); } } @@ -152,9 +157,7 @@ public void addUserRoles(UserRoleRequest req) { * * @param roles list of proposed roles, already validated */ - protected void addRoles(String email, List roles, int groupId, Map studyRoles) { - int userId = verifyUserByEmail(email, groupId); - + protected void addRoles(int userId, List roles, int groupId, Map studyRoles) { for (String role : roles) { if (StringUtils.isBlank(role)) { throw new DsmInternalError("Invalid role: blank"); @@ -163,10 +166,8 @@ protected void addRoles(String email, List roles, int groupId, Map roleNames = req.getRoles(); validateRoles(roleNames, studyRoles.keySet()); - for (String userEmail: req.getUsers()) { - if (StringUtils.isBlank(userEmail)) { - throw new DSMBadRequestException("Invalid user email: blank"); + // pre-check to lessen likelihood of partial operation + Map userIds = new HashMap<>(); + for (String email: req.getUsers()) { + int userId = verifyUserByEmail(email, groupId); + List existingRoles = getRolesForUser(userId, groupId); + if (CollectionUtils.subtract(existingRoles, roleNames).isEmpty()) { + throw new DSMBadRequestException("Cannot remove all roles for user " + email); } - removeUserRoles(userEmail, roleNames, groupId, studyRoles); + userIds.put(email, userId); + } + + for (var entry: userIds.entrySet()) { + removeUserRoles(entry.getValue(), roleNames, groupId, studyRoles); + log.info("Removed roles for user {} in study group {}: {}", entry.getKey(), studyGroup, + String.join(",", roleNames)); } } @@ -196,14 +207,7 @@ public void removeUserRoles(UserRoleRequest req) { * * @param roles list of proposed roles, already validated */ - protected void removeUserRoles(String email, List roles, int groupId, Map studyRoles) { - // TODO: move this to caller in a separate loop - int userId = verifyUserByEmail(email, groupId); - List existingRoles = getRolesForUser(userId, groupId); - if (CollectionUtils.subtract(existingRoles, roles).isEmpty()) { - throw new DSMBadRequestException("Cannot remove all roles for user " + email); - } - + protected void removeUserRoles(int userId, List roles, int groupId, Map studyRoles) { ; for (String role : roles) { if (StringUtils.isBlank(role)) { throw new DsmInternalError("Invalid role: blank"); @@ -213,10 +217,8 @@ protected void removeUserRoles(String email, List roles, int groupId, Ma try { // ignore failure to remove deleteUserRole(userId, roleId, groupId); - String msg = String.format("Removed role %s for user %s in study group %s", role, email, studyGroup); - log.info(msg); } catch (Exception e) { - String msg = String.format("Error removing user %s from role %s for study group %s", email, role, studyGroup); + String msg = String.format("Error removing user %d from role %s for study group %s", userId, role, studyGroup); throw new DsmInternalError(msg, e); } } @@ -280,8 +282,8 @@ public void addUser(AddUserRequest req) { UserDao userDao = new UserDao(); for (var user: users) { - userDao.create(user.asUserDto()); - addRoles(user.getEmail(), user.getRoles(), groupId, studyRoles); + int userId = userDao.create(user.asUserDto()); + addRoles(userId, user.getRoles(), groupId, studyRoles); } } @@ -295,17 +297,11 @@ public void updateUser(UpdateUserRequest req) { Map usersById = new HashMap<>(); // pre-check to lessen likelihood of partial operation for (var user: users) { - String email = validateEmailRequest(user.getEmail()); - // not a strict requirement in DB, but now enforcing if (StringUtils.isBlank(user.getName())) { throw new DSMBadRequestException("Invalid user name: blank"); } - StudyUser studyUser = getUserByEmail(email, groupId); - if (studyUser == null) { - throw new DsmInternalError("User does not exist: " + email); - } - usersById.put(studyUser.getId(), user); + usersById.put(verifyUserByEmail(user.getEmail(), groupId), user); } for (var entry: usersById.entrySet()) { @@ -325,7 +321,6 @@ public void removeUser(UserRequest req) { // pre-check all users to decrease likelihood of incomplete operation List userIds = new ArrayList<>(); for (String email: req.getUsers()) { - validateEmailRequest(email); userIds.add(verifyUserByEmail(email, groupId)); } @@ -622,6 +617,9 @@ protected static int getRoleId(String role) { } protected static int verifyUserByEmail(String email, int groupId) { + if (StringUtils.isBlank(email)) { + throw new DSMBadRequestException("Invalid user email: blank"); + } StudyUser user = getUserByEmail(email, groupId); // TODO: see if access_user.is_active is used -DC if (user == null) { From 1497361bb2ffef9360cb80f67403deaf468aca2c Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 25 Jul 2023 18:21:07 -0400 Subject: [PATCH 32/35] Create and delete user settings; improve user add and remove --- .../broadinstitute/dsm/db/UserSettings.java | 42 ++++++++-- .../dsm/db/dao/user/UserDao.java | 4 +- .../dsm/db/dto/user/UserDto.java | 11 +++ .../dsm/route/OncHistoryUploadRoute.java | 2 +- .../dsm/service/admin/UserAdminService.java | 82 +++++++++---------- .../dsm/service/admin/UserInfo.java | 15 ++-- .../service/admin/UserAdminServiceTest.java | 43 ++++++++-- 7 files changed, 135 insertions(+), 64 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/UserSettings.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/UserSettings.java index 828543183a..498c1647e4 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/UserSettings.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/UserSettings.java @@ -6,9 +6,11 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import lombok.Data; import lombok.NonNull; +import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.statics.DBConstants; import org.broadinstitute.dsm.statics.QueryExtension; import org.broadinstitute.lddp.db.SimpleResult; @@ -27,6 +29,8 @@ public class UserSettings { + "WHERE user.user_id = settings.user_id AND user.is_active = 1"; private static final String SQL_INSERT_USER_SETTINGS = "INSERT INTO user_settings SET user_id = ?"; + private static final String SQL_DELETE_USER_SETTINGS = "DELETE FROM user_settings WHERE user_id = ?"; + private static final String USER_ID = "userId"; private int rowsOnPage; @@ -104,12 +108,38 @@ public static UserSettings getUserSettings(@NonNull String email) { return us; } - public static void insertUserSetting(Connection conn, int userId) { - try (PreparedStatement insertKit = conn.prepareStatement(SQL_INSERT_USER_SETTINGS)) { - insertKit.setInt(1, userId); - insertKit.executeUpdate(); - } catch (SQLException e) { - throw new RuntimeException("Error inserting new user_settings ", e); + public static int createUserSettings(int userId) { + return inTransaction(conn -> insertUserSetting(conn, userId)); + } + + public static int insertUserSetting(Connection conn, int userId) { + int id = -1; + try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER_SETTINGS, Statement.RETURN_GENERATED_KEYS)) { + stmt.setInt(1, userId); + int result = stmt.executeUpdate(); + if (result != 1) { + throw new DsmInternalError("Error inserting user setting. Result count was " + result); + } + try (ResultSet rs = stmt.getGeneratedKeys()) { + if (rs.next()) { + id = rs.getInt(1); + } + } + } catch (SQLException ex) { + throw new DsmInternalError("Error inserting user setting", ex); } + return id; + } + + public static int deleteUserSettings(int userId) { + return inTransaction(conn -> { + try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_SETTINGS)) { + stmt.setInt(1, userId); + return stmt.executeUpdate(); + } catch (SQLException ex) { + String msg = String.format("Error deleting user settings: userId=%d", userId); + throw new DsmInternalError(msg, ex); + } + }); } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java index 5cf21049b6..2007f0974d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dao/user/UserDao.java @@ -89,7 +89,7 @@ public Optional get(long userId) { @Override public int create(UserDto userDto) { - String email = userDto.getEmail().orElse(null); + String email = userDto.getEmailOrThrow(); if (StringUtils.isBlank(email)) { throw new DsmInternalError("Error inserting user: email is blank"); } @@ -113,7 +113,7 @@ public int create(UserDto userDto) { } public static void update(int userId, UserDto userDto) { - String email = userDto.getEmail().orElseThrow(() -> new DsmInternalError("Error updating user: missing email")); + String email = userDto.getEmailOrThrow(); String errorMsg = "Error updating user " + email; int res = inTransaction(conn -> { try (PreparedStatement stmt = conn.prepareStatement(SQL_UPDATE_USER)) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java index 30d8a67340..73e2c55380 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/db/dto/user/UserDto.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Setter; +import org.broadinstitute.dsm.exception.DsmInternalError; @Setter @AllArgsConstructor @@ -36,10 +37,20 @@ public Optional getEmail() { return Optional.ofNullable(email); } + // TODO: getEmail should always throw on a missing email since it is not nullable + // but there are a lot of callers. Feel free to chase them all down and rename this method -DC + public String getEmailOrThrow() { + return getEmail().orElseThrow(() -> new DsmInternalError("User email cannot be null")); + } + public Optional getPhoneNumber() { return Optional.ofNullable(phoneNumber); } public Optional getIsActive() { return Optional.ofNullable(isActive); } + + public boolean isActive() { + return getIsActive().orElse(0) == 1; + } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/OncHistoryUploadRoute.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/OncHistoryUploadRoute.java index d05b2f2e56..a1a1239b71 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/OncHistoryUploadRoute.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/route/OncHistoryUploadRoute.java @@ -40,7 +40,7 @@ protected Object processRequest(Request request, Response response, String userI String oncHistoryUserId; try { UserDto user = new UserDao().get(Integer.parseInt(userId)).orElseThrow(); - oncHistoryUserId = user.getEmail().orElseThrow(); + oncHistoryUserId = user.getEmailOrThrow(); if (oncHistoryUserId.isEmpty()) { throw new DsmInternalError("Empty email address"); } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index a3cc3d2ed7..07af06c2cb 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -13,13 +13,16 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.broadinstitute.dsm.db.UserSettings; import org.broadinstitute.dsm.db.dao.user.UserDao; +import org.broadinstitute.dsm.db.dto.user.UserDto; import org.broadinstitute.dsm.exception.DSMBadRequestException; import org.broadinstitute.dsm.exception.DsmInternalError; import org.broadinstitute.dsm.statics.RoutePath; @@ -71,9 +74,6 @@ public class UserAdminService { private static final String SQL_SELECT_GROUP_ROLE = "SELECT dgr.group_role_id FROM ddp_group_role dgr WHERE dgr.group_id = ? AND dgr.role_id = ?"; - private static final String SQL_SELECT_USER_BY_EMAIL = - "SELECT au.user_id, au.name, au.phone_number, au.is_active FROM access_user au WHERE au.email = ?"; - private static final String SQL_SELECT_USERS_FOR_GROUP = "SELECT au.user_id, au.email, au.name, au.phone_number FROM access_user au " + "JOIN access_user_role_group aurg on aurg.user_id = au.user_id " @@ -141,8 +141,7 @@ public void addUserRoles(UserRoleRequest req) { // pre-check to lessen likelihood of partial operation Map userIds = new HashMap<>(); for (String email: req.getUsers()) { - int userId = verifyUserByEmail(email, groupId); - userIds.put(email, userId); + userIds.put(email, verifyUserByEmail(email, groupId).getId()); } for (var entry: userIds.entrySet()) { @@ -187,7 +186,7 @@ public void removeUserRoles(UserRoleRequest req) { // pre-check to lessen likelihood of partial operation Map userIds = new HashMap<>(); for (String email: req.getUsers()) { - int userId = verifyUserByEmail(email, groupId); + int userId = verifyUserByEmail(email, groupId).getId(); List existingRoles = getRolesForUser(userId, groupId); if (CollectionUtils.subtract(existingRoles, roleNames).isEmpty()) { throw new DSMBadRequestException("Cannot remove all roles for user " + email); @@ -266,6 +265,7 @@ public void addUser(AddUserRequest req) { } // pre-check to lessen likelihood of partial operation + Map emailToId = new HashMap<>(); for (var user: users) { String email = validateEmailRequest(user.getEmail()); @@ -273,8 +273,14 @@ public void addUser(AddUserRequest req) { if (StringUtils.isBlank(user.getName())) { throw new DSMBadRequestException("Invalid user name: blank"); } - if (getUserByEmail(email, groupId) != null) { - throw new DsmInternalError("User already exists: " + email); + UserDto userDto = getUserByEmail(email, groupId); + if (userDto != null) { + if (userDto.isActive()) { + throw new DsmInternalError("User already exists: " + email); + } + log.info("addUser: user {} already exists but is inactive. Activating and updating with new user information", + email); + emailToId.put(userDto.getEmailOrThrow(), userDto.getId()); } validateRoles(user.getRoles(), studyRoles.keySet()); @@ -282,7 +288,13 @@ public void addUser(AddUserRequest req) { UserDao userDao = new UserDao(); for (var user: users) { - int userId = userDao.create(user.asUserDto()); + Integer userId = emailToId.get(user.getEmail()); + if (userId != null) { + UserDao.update(userId, user.asUserDto()); + } else { + userId = userDao.create(user.asUserDto()); + } + UserSettings.createUserSettings(userId); addRoles(userId, user.getRoles(), groupId, studyRoles); } } @@ -301,7 +313,7 @@ public void updateUser(UpdateUserRequest req) { if (StringUtils.isBlank(user.getName())) { throw new DSMBadRequestException("Invalid user name: blank"); } - usersById.put(verifyUserByEmail(user.getEmail(), groupId), user); + usersById.put(verifyUserByEmail(user.getEmail(), groupId).getId(), user); } for (var entry: usersById.entrySet()) { @@ -319,15 +331,16 @@ public void removeUser(UserRequest req) { } // pre-check all users to decrease likelihood of incomplete operation - List userIds = new ArrayList<>(); + List users = new ArrayList<>(); for (String email: req.getUsers()) { - userIds.add(verifyUserByEmail(email, groupId)); + users.add(verifyUserByEmail(email, groupId)); } - UserDao userDao = new UserDao(); - for (int userId: userIds) { - deleteUserRoles(userId); - userDao.delete(userId); + for (UserDto user: users) { + deleteUserRoles(user.getId()); + user.setIsActive(0); + UserDao.update(user.getId(), user); + UserSettings.deleteUserSettings(user.getId()); } } @@ -386,11 +399,8 @@ protected static Map getStudyUsers(int groupId, UserRequest r // theoretically there should be no users without roles for a given study // but there are ways that might occur. log.warn("Found user with no study roles: {}", email); - StudyUser user = getUserByEmail(email, groupId); - if (user == null) { - throw new DSMBadRequestException("Invalid user email " + email); - } - users.put(user.getId(), user.toUserInfo()); + UserDto user = verifyUserByEmail(email, groupId); + users.put(user.getId(), new UserInfo(user)); } } return users; @@ -616,35 +626,25 @@ protected static int getRoleId(String role) { }); } - protected static int verifyUserByEmail(String email, int groupId) { + protected static UserDto verifyUserByEmail(String email, int groupId) { if (StringUtils.isBlank(email)) { throw new DSMBadRequestException("Invalid user email: blank"); } - StudyUser user = getUserByEmail(email, groupId); - // TODO: see if access_user.is_active is used -DC + UserDto user = getUserByEmail(email, groupId); if (user == null) { throw new DSMBadRequestException("Invalid user for study group: " + email); } - return user.getId(); + if (!user.isActive()) { + throw new DSMBadRequestException("Invalid user for study group (inactive): " + email); + } + return user; } - protected static UserAdminService.StudyUser getUserByEmail(String email, int groupId) { + protected static UserDto getUserByEmail(String email, int groupId) { // TODO: Currently we do not track users for a group, but get by groupId once we do -DC - return inTransaction(conn -> { - UserAdminService.StudyUser user = null; - try (PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USER_BY_EMAIL)) { - stmt.setString(1, email); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - user = new StudyUser(rs.getInt(1), email, rs.getString(2), rs.getString(3)); - } - } - } catch (SQLException e) { - String msg = String.format("Error getting user %s for study group, ID: %d", email, groupId); - throw new DsmInternalError(msg, e); - } - return user; - }); + UserDao userDao = new UserDao(); + Optional userDto = userDao.getUserByEmail(email); + return userDto.orElse(null); } protected static int getUserRole(int userId, int roleId, int groupId) { diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java index b74e7032a0..bb9bd99be9 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserInfo.java @@ -3,8 +3,11 @@ import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; import lombok.Data; +import org.broadinstitute.dsm.db.dto.user.UserDto; +@AllArgsConstructor @Data public class UserInfo { private final String email; @@ -12,17 +15,17 @@ public class UserInfo { private final String phone; private List roles; - public UserInfo(String email, String name, String phone, List roles) { + public UserInfo(String email, String name, String phone) { this.email = email; this.name = name; this.phone = phone; - this.roles = roles; + this.roles = new ArrayList<>(); } - public UserInfo(String email, String name, String phone) { - this.email = email; - this.name = name; - this.phone = phone; + public UserInfo(UserDto userDto) { + this.email = userDto.getEmailOrThrow(); + this.name = userDto.getName().orElse(null); + this.phone = userDto.getPhoneNumber().orElse(null); this.roles = new ArrayList<>(); } } diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index d342c13ea2..b029db79d0 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -19,8 +19,8 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.broadinstitute.dsm.DbTxnBaseTest; +import org.broadinstitute.dsm.db.UserSettings; import org.broadinstitute.dsm.db.dao.user.UserDao; import org.broadinstitute.dsm.db.dto.user.UserDto; import org.broadinstitute.dsm.exception.DsmInternalError; @@ -205,8 +205,7 @@ public void testGetUserByEmailAndGroup() { int userId = createTestUser(email, -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { - int id = UserAdminService.verifyUserByEmail(email, groupId); - Assert.assertEquals(userId, id); + Assert.assertEquals(userId, UserAdminService.verifyUserByEmail(email, groupId).getId()); } catch (Exception e) { Assert.fail("Exception from UserAdminService.getUserByEmail: " + getStackTrace(e)); } @@ -395,14 +394,14 @@ public void testAddAndRemoveUser() { String user = "testUser4@study.org"; String userName = "testUser4"; - AddUserRequest req = new AddUserRequest(List.of(new AddUserRequest.User(user, userName, null, + AddUserRequest addUserRequest = new AddUserRequest(List.of(new AddUserRequest.User(user, userName, null, List.of(role1)))); UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); try { - service.addUser(req); + service.addUser(addUserRequest); } catch (Exception e) { - Assert.fail("Exception from UserAdminService.createUser: " + getStackTrace(e)); + Assert.fail("Exception from UserAdminService.addUser: " + getStackTrace(e)); } // add a role @@ -445,6 +444,10 @@ public void testAddAndRemoveUser() { Assert.fail("Exception from UserAdminService.getUserRoles: " + getStackTrace(e)); } + // user should have user settings + UserSettings settings = UserSettings.getUserSettings(user); + Assert.assertNotNull(settings); + userInfoList = res.getUsers(); verifyUserInfo(userInfoList, user, newUserName, phone); @@ -459,11 +462,35 @@ public void testAddAndRemoveUser() { } try { - UserAdminService.verifyUserByEmail(user, -1); + UserAdminService.verifyUserByEmail(user, groupId); Assert.fail("UserAdminService.removeUser failed to remove user"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Invalid user")); } + + try { + service.updateUser(updateReq); + Assert.fail("UserAdminService.updateUser should fail to update a removed user"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid user for study group (inactive)")); + } + + settings = UserSettings.getUserSettings(user); + Assert.assertNull(settings); + + // add user back to test the inactive to active transition + try { + service.addUser(addUserRequest); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUser: " + getStackTrace(e)); + } + + // cleanup + try { + service.removeUser(removeReq); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.removeUser: " + getStackTrace(e)); + } } private static void verifyUserInfo(List userInfoList, String email, String name, String phone) { @@ -575,8 +602,8 @@ private void addAdminRoles(int adminUserId, int groupId, List roles) { private void setUserRoles(String email, List roles, String studyGroup) { try { - int userId = UserAdminService.verifyUserByEmail(email, -1); int groupId = UserAdminService.verifyStudyGroup(studyGroup); + int userId = UserAdminService.verifyUserByEmail(email, groupId).getId(); for (String role : roles) { int roleId = UserAdminService.getRoleId(role); From 8cb0db66b6d30e07feee7a536d2a5179dc0c7284 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 26 Jul 2023 11:15:36 -0400 Subject: [PATCH 33/35] Remove unused subclass --- .../dsm/service/admin/UserAdminService.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 07af06c2cb..6c6707cfd4 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -872,25 +872,6 @@ public RoleInfo(int roleId, String name, String displayText) { } } - @Data - protected static class StudyUser { - private final int id; - private final String email; - private final String name; - private final String phone; - - public StudyUser(int id, String email, String name, String phone) { - this.id = id; - this.email = email; - this.name = name; - this.phone = phone; - } - - public UserInfo toUserInfo() { - return new UserInfo(email, name, phone); - } - } - protected static class NameAndId { public final String name; public final int id; From 6d2581c2d259c75e45075672001afcecca342714 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 27 Jul 2023 11:49:46 -0400 Subject: [PATCH 34/35] Fix typo --- .../org/broadinstitute/dsm/service/admin/UserAdminService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 6c6707cfd4..36b19c4ca8 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -146,7 +146,7 @@ public void addUserRoles(UserRoleRequest req) { for (var entry: userIds.entrySet()) { addRoles(entry.getValue(), roleNames, groupId, studyRoles); - log.info("Removed roles for user {} in study group {}: {}", entry.getKey(), studyGroup, + log.info("Added roles for user {} in study group {}: {}", entry.getKey(), studyGroup, String.join(",", roleNames)); } } From 43e6360ec63ea28d38cd4fc9e05fda70ff328e5d Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 27 Jul 2023 15:40:18 -0400 Subject: [PATCH 35/35] Add support for adding existing users to study --- .../dsm/service/admin/AddUserRequest.java | 15 +++++ .../dsm/service/admin/UserAdminService.java | 65 +++++++++++++------ .../service/admin/UserAdminServiceTest.java | 62 ++++++++++++++++-- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java index 1290eedaef..335210160d 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/AddUserRequest.java @@ -3,7 +3,9 @@ import java.util.List; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import org.broadinstitute.dsm.db.dto.user.UserDto; +import org.broadinstitute.dsm.exception.DsmInternalError; @Data public class AddUserRequest { @@ -31,5 +33,18 @@ public User(String email, String name, String phone, List roles) { public UserDto asUserDto() { return new UserDto(name, email, phone); } + + public UserDto asUpdatedUserDto(UserDto userDto) { + if (!email.equalsIgnoreCase(userDto.getEmailOrThrow())) { + throw new DsmInternalError("Assert: email addresses do not match"); + } + if (!StringUtils.isBlank(name)) { + userDto.setName(name); + } + if (!StringUtils.isBlank(phone)) { + userDto.setPhoneNumber(phone); + } + return userDto; + } } } diff --git a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java index 36b19c4ca8..e43d2f55af 100644 --- a/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java +++ b/pepper-apis/dsm-core/src/main/java/org/broadinstitute/dsm/service/admin/UserAdminService.java @@ -147,7 +147,7 @@ public void addUserRoles(UserRoleRequest req) { for (var entry: userIds.entrySet()) { addRoles(entry.getValue(), roleNames, groupId, studyRoles); log.info("Added roles for user {} in study group {}: {}", entry.getKey(), studyGroup, - String.join(",", roleNames)); + String.join(", ", roleNames)); } } @@ -197,7 +197,7 @@ public void removeUserRoles(UserRoleRequest req) { for (var entry: userIds.entrySet()) { removeUserRoles(entry.getValue(), roleNames, groupId, studyRoles); log.info("Removed roles for user {} in study group {}: {}", entry.getKey(), studyGroup, - String.join(",", roleNames)); + String.join(", ", roleNames)); } } @@ -242,7 +242,7 @@ protected void validateRoles(List roleNames, Set validRoleNames) } Collection badRoles = CollectionUtils.subtract(roleNames, validRoleNames).stream() .map(r -> r.isEmpty() ? "" : r).collect(Collectors.toSet()); - throw new DSMBadRequestException(msg + String.join(",", badRoles)); + throw new DSMBadRequestException(msg + String.join(", ", badRoles)); } protected String validateEmailRequest(String email) { @@ -265,22 +265,22 @@ public void addUser(AddUserRequest req) { } // pre-check to lessen likelihood of partial operation - Map emailToId = new HashMap<>(); + Map emailToUser = new HashMap<>(); for (var user: users) { String email = validateEmailRequest(user.getEmail()); - // not a strict requirement in DB, but now enforcing - if (StringUtils.isBlank(user.getName())) { - throw new DSMBadRequestException("Invalid user name: blank"); - } UserDto userDto = getUserByEmail(email, groupId); if (userDto != null) { - if (userDto.isActive()) { - throw new DsmInternalError("User already exists: " + email); - } - log.info("addUser: user {} already exists but is inactive. Activating and updating with new user information", - email); - emailToId.put(userDto.getEmailOrThrow(), userDto.getId()); + // update any new info provided + emailToUser.put(email, user.asUpdatedUserDto(verifyExistingUser(userDto, groupId))); + } else { + userDto = user.asUserDto(); + emailToUser.put(email, userDto); + } + + // not a strict requirement in DB, but now enforcing + if (StringUtils.isBlank(userDto.getName().orElse(null))) { + throw new DSMBadRequestException("Invalid user name: blank"); } validateRoles(user.getRoles(), studyRoles.keySet()); @@ -288,17 +288,44 @@ public void addUser(AddUserRequest req) { UserDao userDao = new UserDao(); for (var user: users) { - Integer userId = emailToId.get(user.getEmail()); - if (userId != null) { - UserDao.update(userId, user.asUserDto()); + UserDto userDto = emailToUser.get(user.getEmail()); + int userId = userDto.getId(); + boolean hasUserSettings = false; + if (userId != 0) { + UserDao.update(userId, userDto); + hasUserSettings = UserSettings.getUserSettings(user.getEmail()) != null; } else { - userId = userDao.create(user.asUserDto()); + userId = userDao.create(userDto); + } + if (!hasUserSettings) { + UserSettings.createUserSettings(userId); } - UserSettings.createUserSettings(userId); addRoles(userId, user.getRoles(), groupId, studyRoles); } } + protected UserDto verifyExistingUser(UserDto userDto, int groupId) { + String email = userDto.getEmailOrThrow(); + + // if user has no roles in this study then it is okay to add them + List existingRoles = getRolesForUser(userDto.getId(), groupId); + if (!existingRoles.isEmpty()) { + throw new DsmInternalError(String.format("Cannot add user %s: Already has roles in study %s", + email, studyGroup)); + } + + if (userDto.isActive()) { + log.info("addUser: user {} already exists, is active, but has no roles in study {}. " + + "Updating with new user information", + email, studyGroup); + } else { + log.info("addUser: user {} already exists but is inactive. Activating and updating with new user information", + email); + userDto.setIsActive(1); + } + return userDto; + } + public void updateUser(UpdateUserRequest req) { int groupId = validateOperatorAdmin(); List users = req.getUsers(); diff --git a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java index b029db79d0..ac1bef9267 100644 --- a/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java +++ b/pepper-apis/dsm-core/src/test/java/org/broadinstitute/dsm/service/admin/UserAdminServiceTest.java @@ -72,8 +72,8 @@ public static void tearDown() { for (var entry: createdUserRoles.entrySet()) { int userId = entry.getKey(); List userRoles = entry.getValue(); - for (int userRole: userRoles) { - UserAdminService.deleteUserRole(userId, userRole, studyGroupId); + for (int roleId: userRoles) { + UserAdminService.deleteUserRole(userId, roleId, studyGroupId); } userDao.delete(userId); } @@ -201,7 +201,7 @@ public void testVerifyOperatorGroup() { public void testGetUserByEmailAndGroup() { int roleId = UserAdminService.getRoleId("upload_onc_history"); Assert.assertTrue(roleId > 0); - String email = "testUser@study.org"; + String email = "testUser1@study.org"; int userId = createTestUser(email, -1); int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); try { @@ -388,9 +388,7 @@ public void testAddAndRemoveUser() { List roles = List.of(role1, role2); Map rolesToId = getRoleIds(roles); - // include a role for the user, but let the admin service manage it via removeUser - int operatorId = setupAdmin("test_admin3@study.org", List.of(rolesToId.get(role1)), groupId); - addGroupRole(rolesToId.get(role2), userAdminRoleId); + int operatorId = setupAdmin("test_admin3@study.org", new ArrayList<>(rolesToId.values()), groupId); String user = "testUser4@study.org"; String userName = "testUser4"; @@ -477,6 +475,38 @@ public void testAddAndRemoveUser() { settings = UserSettings.getUserSettings(user); Assert.assertNull(settings); + } + + + @Test + public void testAddExistingUser() { + int groupId = UserAdminService.verifyStudyGroup(TEST_GROUP); + + String role1 = "upload_onc_history"; + String role2 = "upload_ror_file"; + List roles = List.of(role1, role2); + Map rolesToId = getRoleIds(roles); + + int operatorId = setupAdmin("test_admin4@study.org", new ArrayList<>(rolesToId.values()), groupId); + + String user = "testUser5@study.org"; + String userName = "testUser5"; + AddUserRequest addUserRequest = new AddUserRequest(List.of(new AddUserRequest.User(user, userName, null, + roles))); + + UserAdminService service = new UserAdminService(Integer.toString(operatorId), TEST_GROUP); + try { + service.addUser(addUserRequest); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUser: " + getStackTrace(e)); + } + + UserRequest removeReq = new UserRequest(List.of(user)); + try { + service.removeUser(removeReq); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.removeUser: " + getStackTrace(e)); + } // add user back to test the inactive to active transition try { @@ -485,6 +515,26 @@ public void testAddAndRemoveUser() { Assert.fail("Exception from UserAdminService.addUser: " + getStackTrace(e)); } + // try to add again + try { + service.addUser(addUserRequest); + Assert.fail("UserAdminService.addUser should fail to add an existing study user"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Already has roles in study")); + } + + // user with no roles in this study can be added + int userId = UserAdminService.verifyUserByEmail(user, groupId).getId(); + for (int roleId: rolesToId.values()) { + UserAdminService.deleteUserRole(userId, roleId, studyGroupId); + } + + try { + service.addUser(addUserRequest); + } catch (Exception e) { + Assert.fail("Exception from UserAdminService.addUser: " + getStackTrace(e)); + } + // cleanup try { service.removeUser(removeReq);