Skip to content

Commit

Permalink
#11928 Limited disease users should not be able to edit event groups … (
Browse files Browse the repository at this point in the history
#13052)

* #11928 Limited disease users should not be able to edit event groups that have at least one event of a different disease
  • Loading branch information
leventegal-she authored Mar 27, 2024
1 parent 9ff8d18 commit 62d4377
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import de.symeda.sormas.api.EditPermissionType;
import de.symeda.sormas.api.common.Page;
import de.symeda.sormas.api.common.progress.ProcessedEntity;
import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto;
Expand Down Expand Up @@ -58,7 +59,7 @@ Page<EventGroupIndexDto> getIndexPage(

void linkEventToGroups(EventReferenceDto eventReference, List<EventGroupReferenceDto> eventGroupReferences);

void linkEventsToGroup(List<EventReferenceDto> eventReferences, EventGroupReferenceDto eventGroupReference);
List<ProcessedEntity> linkEventsToGroup(List<EventReferenceDto> eventReferences, EventGroupReferenceDto eventGroupReference);

List<ProcessedEntity> linkEventsToGroups(List<String> eventUuids, List<String> eventGroupReferences, List<String> alreadyLinkedEventUuidsToGroup);

Expand All @@ -79,4 +80,6 @@ Page<EventGroupIndexDto> getIndexPage(
void notifyEventRemovedFromEventGroup(EventGroupReferenceDto eventGroupReference, List<EventReferenceDto> eventReferences);

List<String> getAlreadyLinkedEventUuidsToGroup(List<String> eventUuids, List<String> eventGroupUuids);

EditPermissionType getEditPermissionType(String uuid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ private static List<Order> getFollowupOrderList(
return orderList;
}

private Expression<Object> jurisdictionSelector(ContactQueryContext qc) {
private Expression<Boolean> jurisdictionSelector(ContactQueryContext qc) {
return JurisdictionHelper.booleanSelector(qc.getCriteriaBuilder(), service.inJurisdictionOrOwned(qc));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import de.symeda.sormas.api.EditPermissionType;
import de.symeda.sormas.api.common.Page;
import de.symeda.sormas.api.common.progress.ProcessedEntity;
import de.symeda.sormas.api.common.progress.ProcessedEntityStatus;
Expand Down Expand Up @@ -332,6 +333,10 @@ public EventGroupDto saveEventGroup(@Valid @NotNull EventGroupDto dto, boolean c
EventGroup existingEventGroup = eventGroupService.getByUuid(dto.getUuid());
FacadeHelper.checkCreateAndEditRights(existingEventGroup, userService, UserRight.EVENTGROUP_CREATE, UserRight.EVENTGROUP_EDIT);

if (!isEditAllowed(dto.getUuid())) {
throw new AccessDeniedException(I18nProperties.getString(Strings.errorAccessDenied));
}

EventGroup eventGroup = fillOrBuildEntity(dto, existingEventGroup, checkChangeDate);

final JurisdictionLevel jurisdictionLevel = currentUser.getJurisdictionLevel();
Expand All @@ -353,15 +358,18 @@ public EventGroupDto saveEventGroup(@Valid @NotNull EventGroupDto dto, boolean c
@Override
@RightsAllowed(UserRight._EVENTGROUP_LINK)
public void linkEventToGroup(EventReferenceDto eventReference, EventGroupReferenceDto eventGroupReference) {
linkEventsToGroup(Collections.singletonList(eventReference), eventGroupReference);
Event event = eventService.getByReferenceDto(eventReference);
EventGroup eventGroup = eventGroupService.getByReferenceDto(eventGroupReference);

linkEventToGroup(event, userService.getCurrentUser(), List.of(eventGroup));
}

@Override
@RightsAllowed(UserRight._EVENTGROUP_LINK)
public void linkEventsToGroup(List<EventReferenceDto> eventReferences, EventGroupReferenceDto eventGroupReference) {
public List<ProcessedEntity> linkEventsToGroup(List<EventReferenceDto> eventReferences, EventGroupReferenceDto eventGroupReference) {
List<String> eventUuids = eventReferences.stream().map(EventReferenceDto::getUuid).collect(Collectors.toList());

linkEventsToGroups(
return linkEventsToGroups(
eventUuids,
Collections.singletonList(eventGroupReference.getUuid()),
getAlreadyLinkedEventUuidsToGroup(eventUuids, Collections.singletonList(eventGroupReference.getUuid())));
Expand Down Expand Up @@ -432,15 +440,14 @@ public void linkEventToGroup(Event event, User currentUser, List<EventGroup> eve
"User " + currentUser.getUuid() + " is not allowed to link events from another region to an event group.");
}

// Check that the event group is not already related to this event
List<EventGroup> filteredEventGroups = getFilteredEventGroups(event, eventGroups);
List<EventGroup> eventGroupsToLinkTo = getEventGroupsNotLinkedTo(event, eventGroups);

if (!filteredEventGroups.isEmpty()) {
if (!eventGroupsToLinkTo.isEmpty()) {
List<EventGroup> groups = new ArrayList<>();
if (event.getEventGroups() != null) {
groups.addAll(event.getEventGroups());
}
groups.addAll(filteredEventGroups);
groups.addAll(eventGroupsToLinkTo);
event.setEventGroups(groups);

eventService.ensurePersisted(event);
Expand All @@ -454,15 +461,24 @@ public List<String> getAlreadyLinkedEventUuidsToGroup(List<String> eventUuids, L
List<EventGroup> filteredEventGroups;
List<String> alreadyLinkedEventUuids = new ArrayList<>();
for (Event event : events) {
filteredEventGroups = getFilteredEventGroups(event, eventGroups);
filteredEventGroups = getEventGroupsNotLinkedTo(event, eventGroups);
if (filteredEventGroups.isEmpty()) {
alreadyLinkedEventUuids.add(event.getUuid());
}
}
return alreadyLinkedEventUuids;
}

public List<EventGroup> getFilteredEventGroups(Event event, List<EventGroup> eventGroups) {
@Override
public EditPermissionType getEditPermissionType(String uuid) {
return eventGroupService.getEditPermissionType(uuid);
}

private boolean isEditAllowed(String uuid) {
return getEditPermissionType(uuid) == EditPermissionType.ALLOWED;
}

public List<EventGroup> getEventGroupsNotLinkedTo(Event event, List<EventGroup> eventGroups) {
List<EventGroup> filteredEventGroups = eventGroups != null ? eventGroups : Collections.emptyList();

// Check that the event group is not already related to this event
Expand All @@ -484,8 +500,7 @@ public void unlinkEventGroup(EventReferenceDto eventReference, EventGroupReferen

final JurisdictionLevel jurisdictionLevel = currentUser.getJurisdictionLevel();
if (!eventFacade.isInJurisdictionOrOwned(event.getUuid()) && (jurisdictionLevel != JurisdictionLevel.NATION) && !currentUser.isAdmin()) {
throw new UnsupportedOperationException(
"User " + currentUser.getUuid() + " is not allowed to unlink events from another region to an event group.");
throw new AccessDeniedException(I18nProperties.getString(Strings.errorAccessDenied));
}

// Check that the event group is not already unlinked to this event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*******************************************************************************/
package de.symeda.sormas.backend.event;

import java.util.List;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
Expand All @@ -32,6 +34,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import de.symeda.sormas.api.EditPermissionType;
import de.symeda.sormas.api.EntityRelevanceStatus;
import de.symeda.sormas.api.event.EventGroupCriteria;
import de.symeda.sormas.api.user.JurisdictionLevel;
Expand All @@ -48,13 +51,16 @@
import de.symeda.sormas.backend.infrastructure.region.Region;
import de.symeda.sormas.backend.location.Location;
import de.symeda.sormas.backend.user.User;
import de.symeda.sormas.backend.util.JurisdictionHelper;

@Stateless
@LocalBean
public class EventGroupService extends AdoServiceWithUserFilterAndJurisdiction<EventGroup> {

@EJB
private CaseService caseService;
@EJB
private EventService eventService;

public EventGroupService() {
super(EventGroup.class);
Expand All @@ -77,12 +83,14 @@ public Predicate createUserFilter(
if (currentUser == null) {
return null;
}

final Join<EventGroup, Event> eventPath = eventGroupPath.join(EventGroup.EVENTS, JoinType.LEFT);

final JurisdictionLevel jurisdictionLevel = currentUser.getJurisdictionLevel();
if (jurisdictionLevel == JurisdictionLevel.NATION) {
return null;
return CriteriaBuilderHelper.limitedDiseasePredicate(cb, currentUser, eventPath.get(Event.DISEASE));
}

Join<EventGroup, Event> eventPath = eventGroupPath.join(EventGroup.EVENTS, JoinType.LEFT);
Predicate filter = null;

switch (jurisdictionLevel) {
Expand All @@ -102,11 +110,8 @@ public Predicate createUserFilter(
}

if (filter != null) {
filter = CriteriaBuilderHelper.and(
cb,
filter,
CriteriaBuilderHelper
.limitedDiseasePredicate(cb, currentUser, eventPath.get(Event.DISEASE), cb.isNull(eventPath.get(Event.DISEASE))));
filter =
CriteriaBuilderHelper.and(cb, filter, CriteriaBuilderHelper.limitedDiseasePredicate(cb, currentUser, eventPath.get(Event.DISEASE)));
}

Predicate filterResponsible = cb.equal(eventPath.join(Event.REPORTING_USER, JoinType.LEFT), currentUser);
Expand Down Expand Up @@ -287,4 +292,19 @@ public Predicate buildCriteriaFilter(EventGroupCriteria eventGroupCriteria, Crit

return filter;
}

public EditPermissionType getEditPermissionType(String uuid) {

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Boolean> cq = cb.createQuery(Boolean.class);
Root<Event> event = cq.from(Event.class);

EventQueryContext eventQueryContext = new EventQueryContext(cb, cq, event);
cq.where(cb.equal(event.join(Event.EVENT_GROUPS).get(EventGroup.UUID), uuid), eventService.createDefaultFilter(cb, event));

cq.select(JurisdictionHelper.booleanSelector(cb, eventService.inJurisdiction(eventQueryContext, getCurrentUser())));

List<Boolean> jurisdictionFlags = em.createQuery(cq).getResultList();
return jurisdictionFlags.stream().allMatch(Boolean::booleanValue) ? EditPermissionType.ALLOWED : EditPermissionType.REFUSED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,8 @@ public List<EventParticipantIndexDto> getIndexList(
sampleDateSq.distinct(true);
sampleDateSq.select(cb.max(sampleSqRoot.get(Sample.SAMPLE_DATE_TIME)));

Expression<Object> inJurisdictionSelector = JurisdictionHelper.booleanSelector(cb, service.inJurisdiction(queryContext));
Expression<Object> inJurisdictionOrOwnedSelector = JurisdictionHelper.booleanSelector(cb, service.inJurisdictionOrOwned(queryContext));
Expression<Boolean> inJurisdictionSelector = JurisdictionHelper.booleanSelector(cb, service.inJurisdiction(queryContext));
Expression<Boolean> inJurisdictionOrOwnedSelector = JurisdictionHelper.booleanSelector(cb, service.inJurisdictionOrOwned(queryContext));

cq.multiselect(
Stream
Expand Down Expand Up @@ -1257,7 +1257,8 @@ public List<SimilarEventParticipantDto> getMatchingEventParticipants(EventPartic
Join<EventParticipant, Person> personJoin = eventParticipantQueryContext.getJoins().getPerson();
Join<EventParticipant, Event> eventJoin = eventParticipantQueryContext.getJoins().getEvent();

Expression<Object> jurisdictionSelector = JurisdictionHelper.booleanSelector(cb, service.inJurisdictionOrOwned(eventParticipantQueryContext));
Expression<Boolean> jurisdictionSelector =
JurisdictionHelper.booleanSelector(cb, service.inJurisdictionOrOwned(eventParticipantQueryContext));
cq.multiselect(
eventParticipantRoot.get(EventParticipant.UUID),
personJoin.get(Person.FIRST_NAME),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public static List<Region> getContactRegions(Contact contact) {
return regions;
}

public static Expression<Object> booleanSelector(CriteriaBuilder cb, Predicate jurisdictionPredicate) {
return cb.selectCase().when(jurisdictionPredicate, cb.literal(true)).otherwise(cb.literal(false));
public static Expression<Boolean> booleanSelector(CriteriaBuilder cb, Predicate jurisdictionPredicate) {
return cb.<Boolean> selectCase().when(jurisdictionPredicate, cb.literal(true)).otherwise(cb.literal(false));
}

public static InfrastructureAdo getParentInfrastructure(InfrastructureAdo infrastructure, JurisdictionLevel parentJurisdictionLevel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ public List<VisitExportDto> getVisitsExportList(
return resultList;
}

private Expression<Object> jurisdictionSelector(VisitQueryContext queryContext) {
private Expression<Boolean> jurisdictionSelector(VisitQueryContext queryContext) {
return JurisdictionHelper.booleanSelector(queryContext.getCriteriaBuilder(), service.inJurisdictionOrOwned(queryContext));
}

Expand Down
2 changes: 1 addition & 1 deletion sormas-backend/src/main/resources/META-INF/persistence.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
<property name="hibernate.criteria.literal_handling_mode" value="bind"/>
<property name="hibernate.transaction.jta.platform"
value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="false"/>

<property name="hibernate.cache.use_second_level_cache" value="true"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,29 @@

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

import de.symeda.sormas.api.Disease;
import de.symeda.sormas.api.event.EventCriteria;
import de.symeda.sormas.api.event.EventDto;
import de.symeda.sormas.api.event.EventGroupCriteria;
import de.symeda.sormas.api.event.EventGroupDto;
import de.symeda.sormas.api.event.EventGroupFacade;
import de.symeda.sormas.api.event.EventGroupIndexDto;
import de.symeda.sormas.api.event.EventIndexDto;
import de.symeda.sormas.api.user.DefaultUserRole;
import de.symeda.sormas.api.user.UserDto;
import de.symeda.sormas.api.user.UserReferenceDto;
import de.symeda.sormas.api.utils.AccessDeniedException;
import de.symeda.sormas.backend.AbstractBeanTest;
import de.symeda.sormas.backend.TestDataCreator.RDCF;
import de.symeda.sormas.backend.event.EventGroupFacadeEjb.EventGroupFacadeEjbLocal;
Expand All @@ -35,21 +49,93 @@ public class EventGroupFacadeEjbTest extends AbstractBeanTest {
@Test
public void testDeleteEventGroup() {

EventGroupFacadeEjb cut = getBean(EventGroupFacadeEjbLocal.class);
EventGroupFacade cut = getEventGroupFacade();

RDCF rdcf = creator.createRDCF();
UserReferenceDto user = creator.createUser(rdcf).toReference();

EventDto event = creator.createEvent(user);

EventGroupDto group = new EventGroupDto();
group.setName("GroupA");
group = cut.saveEventGroup(group);
EventGroupDto group = createEventGroup();
cut.linkEventsToGroup(Collections.singletonList(event.toReference()), group.toReference());

assertThat(cut.getEventGroupByUuid(group.getUuid()), equalTo(group));

cut.deleteEventGroup(group.getUuid());
assertNull(cut.getEventGroupByUuid(group.getUuid()));
}

@Test
public void testEditEventGroupWithEventsOutsideJurisdiction() {
EventGroupFacadeEjb eventGroupFacade = getBean(EventGroupFacadeEjbLocal.class);
RDCF rdcf = creator.createRDCF();
UserReferenceDto user = creator.createUser(rdcf).toReference();

EventDto event = creator.createEvent(user);

EventGroupDto eventGroup = createEventGroup();

eventGroupFacade.linkEventToGroup(event.toReference(), eventGroup.toReference());

RDCF rdcf2 = creator.createRDCF();
UserDto survOff = creator.createUser(rdcf2, DefaultUserRole.SURVEILLANCE_OFFICER);

loginWith(survOff);

EventDto survOffEvent = creator.createEvent(survOff.toReference());
eventGroupFacade.linkEventToGroup(survOffEvent.toReference(), eventGroup.toReference());

eventGroup.setName("NewName");
assertThrows(AccessDeniedException.class, () -> eventGroupFacade.saveEventGroup(eventGroup));

assertThrows(AccessDeniedException.class, () -> eventGroupFacade.unlinkEventGroup(event.toReference(), eventGroup.toReference()));

eventGroupFacade.unlinkEventGroup(survOffEvent.toReference(), eventGroup.toReference());

EventCriteria eventsByGroupCriteria = new EventCriteria().eventGroup(eventGroup.toReference());
eventsByGroupCriteria.setUserFilterIncluded(false);
List<EventIndexDto> eventsByGroup = getEventFacade().getIndexList(eventsByGroupCriteria, 0, 10, null);

assertThat(eventsByGroup, hasSize(1));
assertThat(eventsByGroup.get(0).getUuid(), Matchers.is(event.getUuid()));
}

@Test
public void testGetIndexListWithNationalUserLimitedToCovid() {
EventGroupFacadeEjb eventGroupFacade = getBean(EventGroupFacadeEjbLocal.class);
RDCF rdcf = creator.createRDCF();
UserReferenceDto user = creator.createUser(rdcf).toReference();

// create a group with a cholera event
EventDto event = creator.createEvent(user, Disease.CHOLERA);
EventGroupDto eventGroup = createEventGroup();
eventGroupFacade.linkEventToGroup(event.toReference(), eventGroup.toReference());

//create a group with covid event
EventDto event2 = creator.createEvent(user, Disease.CORONAVIRUS);
EventGroupDto eventGroup2 = createEventGroup();
eventGroupFacade.linkEventToGroup(event2.toReference(), eventGroup2.toReference());

RDCF rdcf2 = creator.createRDCF();
UserDto natUser = creator.createUser(rdcf2, creator.getUserRoleReference(DefaultUserRole.NATIONAL_USER), u -> {
u.setLimitedDiseases(Set.of(Disease.CORONAVIRUS));
});
loginWith(natUser);

List<EventGroupIndexDto> indexList = eventGroupFacade.getIndexList(new EventGroupCriteria(), 0, 10, null);

assertThat(indexList, hasSize(1));
assertThat(indexList.get(0).getUuid(), Matchers.is(eventGroup2.getUuid()));
}

private EventGroupDto createEventGroup() {
EventGroupDto group = new EventGroupDto();
group.setName("GroupA");

return getEventGroupFacade().saveEventGroup(group);
}

private EventGroupFacade getEventGroupFacade() {
return getBean(EventGroupFacadeEjbLocal.class);
}
}
Loading

0 comments on commit 62d4377

Please sign in to comment.