Closes #306: Prevent deleting/renaming users/groups used in route models

This commit is contained in:
Benjamin Gamard 2019-05-02 16:19:50 +02:00
parent cea0d4887d
commit 58bc374e64
8 changed files with 109 additions and 13 deletions

View File

@ -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<RouteModel> 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.
*

View File

@ -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<RouteModel> 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;
}
}

View File

@ -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());

View File

@ -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();

View File

@ -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);
}
});
}
});

View File

@ -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);
}
});
}
});

View File

@ -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",

View File

@ -325,7 +325,7 @@
"type_approve": "Approbation",
"type_validate": "Validation",
"target": "Assigné à",
"target_help": "<strong>Approbation :</strong> Accepter ou rejeter l'étape de workflow<br/><strong>Validation :</strong> Examiner et poursuivre le workflow",
"target_help": "<strong>Approbation :</strong> Accepter ou rejeter l\\'étape de workflow<br/><strong>Validation :</strong> 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",