Skip to content

Commit 20bb935

Browse files
authored
fix: Move TE request validations to appropiate layers [TECH-1656] (#15732)
* fix: Move TE request validations to appropiate layers [TECH-1656] * fix: Refactor tracked entities tests [TECH-1656] * fix: Refactor tracked entities tests [TECH-1656] * fix: Split general TE request mapping test in two [TECH-1656] * fix: Merge validateUser method into existing ones [TECH-1656] * fix: Refactor trackedEntities tests [TECH-1656] * fix: Refactor global search validation [TECH-1656] * fix: Format code [TECH-1656] * fix: Format code [TECH-1656] * fix: Fix broken test after refactor [TECH-1656] * fix: Removed TODO comment [TECH-1656] * fix: Minor refactor after PR [TECH-1656] * fix: Minor refactor after PR [TECH-1656] * fix: Update error messages according to new params naming [TECH-1656] * fix: Use program tet if available [TECH-1656] * fix: Fix test error message [TECH-1656] * fix: Add validation to have program or tei info in request [TECH-1656] * fix: Remove exception when fetching tracked entity types [TECH-1656] * fix: Remove exception when fetching tracked entity types [TECH-1656] * fix: Use correct value of trackedEntities on validation [TECH-1656] * fix: Throw exception if user has no access to any TET [TECH-1656] * fix: Add TET to program integration test TET [TECH-1656] * fix: Add test to validate exception when no TET found [TECH-1656]
1 parent 8ba5652 commit 20bb935

File tree

10 files changed

+499
-332
lines changed

10 files changed

+499
-332
lines changed

dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/OperationsParamsValidator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ private static void validateCaptureScope(User user) throws BadRequestException {
9191
if (user == null) {
9292
throw new BadRequestException("User is required for orgUnitMode: " + CAPTURE);
9393
} else if (user.getOrganisationUnits().isEmpty()) {
94-
throw new BadRequestException("User needs to be assigned data capture orgunits");
94+
throw new BadRequestException("User needs to be assigned data capture org units");
9595
}
9696
}
9797
}

dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java

-301
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@
2727
*/
2828
package org.hisp.dhis.tracker.export.trackedentity;
2929

30-
import static org.hisp.dhis.common.OrganisationUnitSelectionMode.ALL;
31-
import static org.hisp.dhis.common.OrganisationUnitSelectionMode.CHILDREN;
32-
import static org.hisp.dhis.common.OrganisationUnitSelectionMode.DESCENDANTS;
33-
import static org.hisp.dhis.common.OrganisationUnitSelectionMode.SELECTED;
34-
35-
import java.util.ArrayList;
3630
import java.util.HashSet;
3731
import java.util.LinkedHashSet;
3832
import java.util.List;
@@ -48,20 +42,15 @@
4842
import org.hisp.dhis.common.AccessLevel;
4943
import org.hisp.dhis.common.AuditType;
5044
import org.hisp.dhis.common.BaseIdentifiableObject;
51-
import org.hisp.dhis.common.IllegalQueryException;
5245
import org.hisp.dhis.feedback.BadRequestException;
5346
import org.hisp.dhis.feedback.ForbiddenException;
5447
import org.hisp.dhis.feedback.NotFoundException;
55-
import org.hisp.dhis.organisationunit.OrganisationUnit;
56-
import org.hisp.dhis.organisationunit.OrganisationUnitService;
5748
import org.hisp.dhis.program.Enrollment;
5849
import org.hisp.dhis.program.Event;
5950
import org.hisp.dhis.program.Program;
6051
import org.hisp.dhis.program.ProgramService;
6152
import org.hisp.dhis.relationship.Relationship;
6253
import org.hisp.dhis.relationship.RelationshipItem;
63-
import org.hisp.dhis.security.Authorities;
64-
import org.hisp.dhis.security.acl.AclService;
6554
import org.hisp.dhis.trackedentity.TrackedEntity;
6655
import org.hisp.dhis.trackedentity.TrackedEntityAttribute;
6756
import org.hisp.dhis.trackedentity.TrackedEntityAttributeService;
@@ -81,7 +70,6 @@
8170
import org.hisp.dhis.tracker.export.trackedentity.aggregates.TrackedEntityAggregate;
8271
import org.hisp.dhis.user.CurrentUserService;
8372
import org.hisp.dhis.user.User;
84-
import org.hisp.dhis.util.DateUtils;
8573
import org.springframework.stereotype.Service;
8674
import org.springframework.transaction.annotation.Transactional;
8775

@@ -99,16 +87,12 @@ class DefaultTrackedEntityService implements TrackedEntityService {
9987

10088
private final TrackedEntityAuditService trackedEntityAuditService;
10189

102-
private final OrganisationUnitService organisationUnitService;
103-
10490
private final CurrentUserService currentUserService;
10591

10692
private final TrackerAccessManager trackerAccessManager;
10793

10894
private final TrackedEntityAggregate trackedEntityAggregate;
10995

110-
private final AclService aclService;
111-
11296
private final ProgramService programService;
11397

11498
private final EnrollmentService enrollmentService;
@@ -343,298 +327,13 @@ public Page<TrackedEntity> getTrackedEntities(
343327
}
344328

345329
public List<Long> getTrackedEntityIds(TrackedEntityQueryParams params) {
346-
decideAccess(params);
347-
validate(params);
348-
validateSearchScope(params);
349-
350330
return trackedEntityStore.getTrackedEntityIds(params);
351331
}
352332

353333
public Page<Long> getTrackedEntityIds(TrackedEntityQueryParams params, PageParams pageParams) {
354-
decideAccess(params);
355-
validate(params);
356-
validateSearchScope(params);
357-
358334
return trackedEntityStore.getTrackedEntityIds(params, pageParams);
359335
}
360336

361-
public void decideAccess(TrackedEntityQueryParams params) {
362-
if (params.isOrganisationUnitMode(ALL)
363-
&& !currentUserService.currentUserIsAuthorized(
364-
Authorities.F_TRACKED_ENTITY_INSTANCE_SEARCH_IN_ALL_ORGUNITS.name())) {
365-
throw new IllegalQueryException(
366-
"Current user is not authorized to query across all organisation units");
367-
}
368-
369-
User user = params.getUser();
370-
if (params.hasProgram()) {
371-
if (!aclService.canDataRead(user, params.getProgram())) {
372-
throw new IllegalQueryException(
373-
"Current user is not authorized to read data from selected program: "
374-
+ params.getProgram().getUid());
375-
}
376-
377-
if (params.getProgram().getTrackedEntityType() != null
378-
&& !aclService.canDataRead(user, params.getProgram().getTrackedEntityType())) {
379-
throw new IllegalQueryException(
380-
"Current user is not authorized to read data from selected program's tracked entity type: "
381-
+ params.getProgram().getTrackedEntityType().getUid());
382-
}
383-
}
384-
385-
if (params.hasTrackedEntityType()
386-
&& !aclService.canDataRead(user, params.getTrackedEntityType())) {
387-
throw new IllegalQueryException(
388-
"Current user is not authorized to read data from selected tracked entity type: "
389-
+ params.getTrackedEntityType().getUid());
390-
} else {
391-
params.setTrackedEntityTypes(
392-
trackedEntityTypeService.getAllTrackedEntityType().stream()
393-
.filter(tet -> aclService.canDataRead(user, tet))
394-
.collect(Collectors.toList()));
395-
}
396-
}
397-
398-
public void validate(TrackedEntityQueryParams params) throws IllegalQueryException {
399-
String violation = null;
400-
401-
if (params == null) {
402-
throw new IllegalQueryException("Params cannot be null");
403-
}
404-
405-
if (params.hasProgram() && params.hasTrackedEntityType()) {
406-
violation = "Program and tracked entity cannot be specified simultaneously";
407-
}
408-
409-
if (!params.hasTrackedEntities() && !params.hasProgram() && !params.hasTrackedEntityType()) {
410-
violation = "Either Program or Tracked entity type should be specified";
411-
}
412-
413-
if (params.hasProgramStatus() && !params.hasProgram()) {
414-
violation = "Program must be defined when program status is defined";
415-
}
416-
417-
if (params.hasFollowUp() && !params.hasProgram()) {
418-
violation = "Program must be defined when follow up status is defined";
419-
}
420-
421-
if (params.hasProgramEnrollmentStartDate() && !params.hasProgram()) {
422-
violation = "Program must be defined when program enrollment start date is specified";
423-
}
424-
425-
if (params.hasProgramEnrollmentEndDate() && !params.hasProgram()) {
426-
violation = "Program must be defined when program enrollment end date is specified";
427-
}
428-
429-
if (params.hasProgramIncidentStartDate() && !params.hasProgram()) {
430-
violation = "Program must be defined when program incident start date is specified";
431-
}
432-
433-
if (params.hasProgramIncidentEndDate() && !params.hasProgram()) {
434-
violation = "Program must be defined when program incident end date is specified";
435-
}
436-
437-
if (params.hasEventStatus() && (!params.hasEventStartDate() || !params.hasEventEndDate())) {
438-
violation = "Event start and end date must be specified when event status is specified";
439-
}
440-
441-
if (params.hasLastUpdatedDuration()
442-
&& (params.hasLastUpdatedStartDate() || params.hasLastUpdatedEndDate())) {
443-
violation =
444-
"Last updated from and/or to and last updated duration cannot be specified simultaneously";
445-
}
446-
447-
if (params.hasLastUpdatedDuration()
448-
&& DateUtils.getDuration(params.getLastUpdatedDuration()) == null) {
449-
violation = "Duration is not valid: " + params.getLastUpdatedDuration();
450-
}
451-
452-
if (violation != null) {
453-
log.warn("Validation failed: " + violation);
454-
455-
throw new IllegalQueryException(violation);
456-
}
457-
}
458-
459-
public void validateSearchScope(TrackedEntityQueryParams params) throws IllegalQueryException {
460-
if (params == null) {
461-
throw new IllegalQueryException("Params cannot be null");
462-
}
463-
464-
User user = currentUserService.getCurrentUser();
465-
466-
if (user == null) {
467-
throw new IllegalQueryException("User cannot be null");
468-
}
469-
470-
if (!user.isSuper() && user.getOrganisationUnits().isEmpty()) {
471-
throw new IllegalQueryException(
472-
"User need to be associated with at least one organisation unit.");
473-
}
474-
475-
if (!params.hasProgram()
476-
&& !params.hasTrackedEntityType()
477-
&& params.hasFilters()
478-
&& !params.hasOrganisationUnits()) {
479-
List<String> uniqueAttributeIds =
480-
trackedEntityAttributeService.getAllSystemWideUniqueTrackedEntityAttributes().stream()
481-
.map(TrackedEntityAttribute::getUid)
482-
.toList();
483-
484-
for (String att : params.getFilterIds()) {
485-
if (!uniqueAttributeIds.contains(att)) {
486-
throw new IllegalQueryException(
487-
"Either a program or tracked entity type must be specified");
488-
}
489-
}
490-
}
491-
492-
if (!isLocalSearch(params, user)) {
493-
int maxTeiLimit = 0; // no limit
494-
495-
if (params.hasProgram() && params.hasTrackedEntityType()) {
496-
throw new IllegalQueryException(
497-
"Program and tracked entity cannot be specified simultaneously");
498-
}
499-
500-
if (params.hasFilters()) {
501-
List<String> searchableAttributeIds = new ArrayList<>();
502-
503-
if (params.hasProgram()) {
504-
searchableAttributeIds.addAll(params.getProgram().getSearchableAttributeIds());
505-
}
506-
507-
if (params.hasTrackedEntityType()) {
508-
searchableAttributeIds.addAll(params.getTrackedEntityType().getSearchableAttributeIds());
509-
}
510-
511-
if (!params.hasProgram() && !params.hasTrackedEntityType()) {
512-
searchableAttributeIds.addAll(
513-
trackedEntityAttributeService.getAllSystemWideUniqueTrackedEntityAttributes().stream()
514-
.map(TrackedEntityAttribute::getUid)
515-
.toList());
516-
}
517-
518-
List<String> violatingAttributes = new ArrayList<>();
519-
520-
for (String attributeId : params.getFilterIds()) {
521-
if (!searchableAttributeIds.contains(attributeId)) {
522-
violatingAttributes.add(attributeId);
523-
}
524-
}
525-
526-
if (!violatingAttributes.isEmpty()) {
527-
throw new IllegalQueryException(
528-
"Non-searchable attribute(s) can not be used during global search: "
529-
+ violatingAttributes);
530-
}
531-
}
532-
533-
if (params.hasTrackedEntityType()) {
534-
maxTeiLimit = params.getTrackedEntityType().getMaxTeiCountToReturn();
535-
536-
if (!params.hasTrackedEntities() && isTeTypeMinAttributesViolated(params)) {
537-
throw new IllegalQueryException(
538-
"At least "
539-
+ params.getTrackedEntityType().getMinAttributesRequiredToSearch()
540-
+ " attributes should be mentioned in the search criteria.");
541-
}
542-
}
543-
544-
if (params.hasProgram()) {
545-
maxTeiLimit = params.getProgram().getMaxTeiCountToReturn();
546-
547-
if (!params.hasTrackedEntities() && isProgramMinAttributesViolated(params)) {
548-
throw new IllegalQueryException(
549-
"At least "
550-
+ params.getProgram().getMinAttributesRequiredToSearch()
551-
+ " attributes should be mentioned in the search criteria.");
552-
}
553-
}
554-
555-
checkIfMaxTeiLimitIsReached(params, maxTeiLimit);
556-
params.setMaxTeLimit(maxTeiLimit);
557-
}
558-
}
559-
560-
private boolean isLocalSearch(TrackedEntityQueryParams params, User user) {
561-
Set<OrganisationUnit> localOrgUnits = user.getOrganisationUnits();
562-
563-
Set<OrganisationUnit> searchOrgUnits = new HashSet<>();
564-
565-
if (params.isOrganisationUnitMode(SELECTED)) {
566-
searchOrgUnits = params.getOrgUnits();
567-
} else if (params.isOrganisationUnitMode(CHILDREN)
568-
|| params.isOrganisationUnitMode(DESCENDANTS)) {
569-
for (OrganisationUnit orgUnit : params.getOrgUnits()) {
570-
searchOrgUnits.addAll(orgUnit.getChildren());
571-
}
572-
} else if (params.isOrganisationUnitMode(ALL)) {
573-
searchOrgUnits.addAll(organisationUnitService.getRootOrganisationUnits());
574-
} else {
575-
searchOrgUnits.addAll(user.getTeiSearchOrganisationUnitsWithFallback());
576-
}
577-
578-
for (OrganisationUnit ou : searchOrgUnits) {
579-
if (!ou.isDescendant(localOrgUnits)) {
580-
return false;
581-
}
582-
}
583-
584-
return true;
585-
}
586-
587-
private boolean isTeTypeMinAttributesViolated(TrackedEntityQueryParams params) {
588-
if (params.hasUniqueFilter()) {
589-
return false;
590-
}
591-
592-
return (!params.hasFilters()
593-
&& params.getTrackedEntityType().getMinAttributesRequiredToSearch() > 0)
594-
|| (params.hasFilters()
595-
&& params.getFilters().size()
596-
< params.getTrackedEntityType().getMinAttributesRequiredToSearch());
597-
}
598-
599-
private boolean isProgramMinAttributesViolated(TrackedEntityQueryParams params) {
600-
if (params.hasUniqueFilter()) {
601-
return false;
602-
}
603-
604-
return (!params.hasFilters() && params.getProgram().getMinAttributesRequiredToSearch() > 0)
605-
|| (params.hasFilters()
606-
&& params.getFilters().size() < params.getProgram().getMinAttributesRequiredToSearch());
607-
}
608-
609-
private void checkIfMaxTeiLimitIsReached(TrackedEntityQueryParams params, int maxTeiLimit) {
610-
if (maxTeiLimit > 0) {
611-
int teCount = trackedEntityStore.getTrackedEntityCountWithMaxTrackedEntityLimit(params);
612-
613-
if (teCount > maxTeiLimit) {
614-
throw new IllegalQueryException("maxteicountreached");
615-
}
616-
}
617-
}
618-
619-
public int getTrackedEntityCount(
620-
TrackedEntityQueryParams params,
621-
boolean skipAccessValidation,
622-
boolean skipSearchScopeValidation) {
623-
decideAccess(params);
624-
625-
if (!skipAccessValidation) {
626-
validate(params);
627-
}
628-
629-
if (!skipSearchScopeValidation) {
630-
validateSearchScope(params);
631-
}
632-
633-
// using countForGrid here to leverage the better performant rewritten
634-
// sql query
635-
return trackedEntityStore.getTrackedEntityCount(params);
636-
}
637-
638337
/**
639338
* We need to return the full models for relationship items (i.e. trackedEntity, enrollment and
640339
* event) in our API. The aggregate stores currently do not support that, so we need to fetch the

0 commit comments

Comments
 (0)