From 58bc374e640b5848527182a35ad9bfb57772e8b6 Mon Sep 17 00:00:00 2001 From: Benjamin Gamard Date: Thu, 2 May 2019 16:19:50 +0200 Subject: [PATCH] Closes #306: Prevent deleting/renaming users/groups used in route models --- .../sismics/docs/core/dao/RouteModelDao.java | 13 +++++++ .../sismics/docs/core/util/RoutingUtil.java | 34 +++++++++++++++++++ .../docs/rest/resource/GroupResource.java | 18 +++++++++- .../docs/rest/resource/UserResource.java | 16 ++++++++- .../controller/settings/SettingsGroupEdit.js | 26 +++++++++----- .../controller/settings/SettingsUserEdit.js | 9 +++-- docs-web/src/main/webapp/src/locale/en.json | 4 +++ docs-web/src/main/webapp/src/locale/fr.json | 2 +- 8 files changed, 109 insertions(+), 13 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/RouteModelDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/RouteModelDao.java index af640875..63952aaa 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/RouteModelDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/RouteModelDao.java @@ -4,6 +4,7 @@ import com.google.common.base.Joiner; import com.sismics.docs.core.constant.AuditLogType; import com.sismics.docs.core.dao.criteria.RouteModelCriteria; import com.sismics.docs.core.dao.dto.RouteModelDto; +import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.model.jpa.RouteModel; import com.sismics.docs.core.util.AuditLogUtil; import com.sismics.docs.core.util.SecurityUtil; @@ -88,6 +89,18 @@ public class RouteModelDao { } } + /** + * Returns the list of all route models. + * + * @return List of route models + */ + @SuppressWarnings("unchecked") + public List findAll() { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + Query q = em.createQuery("select r from RouteModel r where r.deleteDate is null"); + return q.getResultList(); + } + /** * Deletes a route model. * diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java index b417d755..990cfc14 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java @@ -6,6 +6,7 @@ import com.sismics.docs.core.constant.AclType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.AclDao; import com.sismics.docs.core.dao.DocumentDao; +import com.sismics.docs.core.dao.RouteModelDao; import com.sismics.docs.core.dao.UserDao; import com.sismics.docs.core.dao.criteria.UserCriteria; import com.sismics.docs.core.dao.dto.RouteStepDto; @@ -15,8 +16,14 @@ import com.sismics.docs.core.event.RouteStepValidateEvent; import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.Document; +import com.sismics.docs.core.model.jpa.RouteModel; import com.sismics.util.context.ThreadLocalContext; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; import java.util.List; /** @@ -87,4 +94,31 @@ public class RoutingUtil { AppContext.getInstance().getMailEventBus().post(routeStepValidateEvent); } } + + /** + * Find the first route model name matching a target type and name. + * + * @param targetType Target type + * @param targetName Target name + * @return Route model name or null if none is matching + */ + public static String findRouteModelNameByTargetName(AclTargetType targetType, String targetName) { + RouteModelDao routeModelDao = new RouteModelDao(); + List routeModelList = routeModelDao.findAll(); + for (RouteModel routeModel : routeModelList) { + try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) { + JsonArray stepsJson = reader.readArray(); + for (int order = 0; order < stepsJson.size(); order++) { + JsonObject step = stepsJson.getJsonObject(order); + JsonObject target = step.getJsonObject("target"); + AclTargetType routeTargetType = AclTargetType.valueOf(target.getString("type")); + String routeTargetName = target.getString("name"); + if (targetType == routeTargetType && targetName.equals(routeTargetName)) { + return routeModel.getName(); + } + } + } + } + return null; + } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java index 6e9e7b58..b3e2f943 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java @@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource; import com.google.common.base.Strings; import com.google.common.collect.Sets; +import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.dao.GroupDao; import com.sismics.docs.core.dao.RoleBaseFunctionDao; import com.sismics.docs.core.dao.UserDao; @@ -12,6 +13,7 @@ import com.sismics.docs.core.dao.dto.UserDto; import com.sismics.docs.core.model.jpa.Group; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.model.jpa.UserGroup; +import com.sismics.docs.core.util.RoutingUtil; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.docs.rest.constant.BaseFunction; import com.sismics.rest.exception.ClientException; @@ -148,6 +150,14 @@ public class GroupResource extends BaseResource { } parentId = parentGroup.getId(); } + + // Check that this group is not used in any workflow in case of renaming + if (!name.equals(groupName)) { + String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName); + if (routeModelName != null) { + throw new ClientException("GroupUsedInRouteModel", routeModelName); + } + } // Update the group groupDao.update(group.setName(name) @@ -197,7 +207,13 @@ public class GroupResource extends BaseResource { throw new ClientException("ForbiddenError", "The administrators group cannot be deleted"); } } - + + // Check that this group is not used in any workflow + String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName); + if (routeModelName != null) { + throw new ClientException("GroupUsedInRouteModel", routeModelName); + } + // Delete the group groupDao.delete(group.getId(), principal.getId()); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index a851da2c..c16c4625 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource; import com.google.common.base.Strings; import com.google.common.collect.Sets; +import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.ConfigType; import com.sismics.docs.core.constant.Constants; import com.sismics.docs.core.dao.*; @@ -15,6 +16,7 @@ import com.sismics.docs.core.event.PasswordLostEvent; import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.jpa.*; import com.sismics.docs.core.util.ConfigUtil; +import com.sismics.docs.core.util.RoutingUtil; import com.sismics.docs.core.util.authentication.AuthenticationUtil; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.docs.rest.constant.BaseFunction; @@ -449,6 +451,12 @@ public class UserResource extends BaseResource { if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) { throw new ClientException("ForbiddenError", "This user cannot be deleted"); } + + // Check that this user is not used in any workflow + String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, principal.getName()); + if (routeModelName != null) { + throw new ClientException("UserUsedInRouteModel", routeModelName); + } // Find linked data DocumentDao documentDao = new DocumentDao(); @@ -512,7 +520,7 @@ public class UserResource extends BaseResource { throw new ClientException("ForbiddenError", "The guest user cannot be deleted"); } - // Check if the user exists + // Check that the user exists UserDao userDao = new UserDao(); User user = userDao.getActiveByUsername(username); if (user == null) { @@ -525,6 +533,12 @@ public class UserResource extends BaseResource { if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) { throw new ClientException("ForbiddenError", "The admin user cannot be deleted"); } + + // Check that this user is not used in any workflow + String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, username); + if (routeModelName != null) { + throw new ClientException("UserUsedInRouteModel", routeModelName); + } // Find linked data DocumentDao documentDao = new DocumentDao(); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsGroupEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsGroupEdit.js index b3aee57a..ffcc0b5d 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsGroupEdit.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsGroupEdit.js @@ -46,12 +46,17 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog, $state.go('settings.group.edit', { name: group.name }); } }, function (e) { - if (e.data.type === 'GroupAlreadyExists') { - var title = $translate.instant('settings.group.edit.edit_group_failed_title'); - var msg = $translate.instant('settings.group.edit.edit_group_failed_message'); - var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}]; - $dialog.messageBox(title, msg, btns); - } + if (e.data.type === 'GroupAlreadyExists') { + var title = $translate.instant('settings.group.edit.edit_group_failed_title'); + var msg = $translate.instant('settings.group.edit.edit_group_failed_message'); + var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}]; + $dialog.messageBox(title, msg, btns); + } else if (e.data.type === 'GroupUsedInRouteModel') { + var title = $translate.instant('settings.group.edit.group_used_title'); + var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message }); + var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}]; + $dialog.messageBox(title, msg, btns); + } }); }; @@ -71,8 +76,13 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog, Restangular.one('group', $stateParams.name).remove().then(function() { $scope.loadGroups(); $state.go('settings.group'); - }, function() { - $state.go('settings.group'); + }, function(e) { + if (e.data.type === 'GroupUsedInRouteModel') { + var title = $translate.instant('settings.group.edit.group_used_title'); + var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message }); + var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}]; + $dialog.messageBox(title, msg, btns); + } }); } }); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsUserEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsUserEdit.js index 87cf9ca3..90e2828d 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsUserEdit.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsUserEdit.js @@ -70,8 +70,13 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog, Restangular.one('user', $stateParams.username).remove().then(function () { $scope.loadUsers(); $state.go('settings.user'); - }, function() { - $state.go('settings.user'); + }, function(e) { + if (e.data.type === 'UserUsedInRouteModel') { + var title = $translate.instant('settings.user.edit.user_used_title'); + var msg = $translate.instant('settings.user.edit.user_used_message', { name: e.data.message }); + var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}]; + $dialog.messageBox(title, msg, btns); + } }); } }); diff --git a/docs-web/src/main/webapp/src/locale/en.json b/docs-web/src/main/webapp/src/locale/en.json index ede49e6e..d14dc89f 100644 --- a/docs-web/src/main/webapp/src/locale/en.json +++ b/docs-web/src/main/webapp/src/locale/en.json @@ -288,6 +288,8 @@ "edit": { "delete_user_title": "Delete user", "delete_user_message": "Do you really want to delete this user? All associated documents, files and tags will be deleted", + "user_used_title": "User in use", + "user_used_message": "This user is used in the workflow \"{{ name }}\"", "edit_user_failed_title": "User already exists", "edit_user_failed_message": "This username is already taken by another user", "edit_user_title": "Edit \"{{ username }}\"", @@ -363,6 +365,8 @@ "delete_group_message": "Do you really want to delete this group?", "edit_group_failed_title": "Group already exists", "edit_group_failed_message": "This group name is already taken by another group", + "group_used_title": "Group in use", + "group_used_message": "This group is used in the workflow \"{{ name }}\"", "edit_group_title": "Edit \"{{ name }}\"", "add_group_title": "Add a group", "name": "Name", diff --git a/docs-web/src/main/webapp/src/locale/fr.json b/docs-web/src/main/webapp/src/locale/fr.json index d579f7fe..d3a5adb4 100644 --- a/docs-web/src/main/webapp/src/locale/fr.json +++ b/docs-web/src/main/webapp/src/locale/fr.json @@ -325,7 +325,7 @@ "type_approve": "Approbation", "type_validate": "Validation", "target": "Assigné à", - "target_help": "Approbation : Accepter ou rejeter l'étape de workflow
Validation : Examiner et poursuivre le workflow", + "target_help": "Approbation : Accepter ou rejeter l\\'étape de workflow
Validation : Examiner et poursuivre le workflow", "add_step": "Ajouter une étape de workflow", "actions": "Qu'est-ce qui se passe après?", "remove_action": "Supprimer l'action",