Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Move TE request validations to appropiate layers [TECH-1656] #15732

Merged

Conversation

muilpp
Copy link
Contributor

@muilpp muilpp commented Nov 17, 2023

We had a few validations regarding tracked entities that were scattered around the service, outside the mapper. They were mainly decideAccess(), validate(), and validateSearchScope().
Now the three of them are split and all the contained validations are in the mapper.

Ticket: https://dhis2.atlassian.net/browse/TECH-1656

Copy link

codecov bot commented Nov 21, 2023

Codecov Report

Merging #15732 (30c26f4) into master (a3ecb7a) will increase coverage by 0.01%.
The diff coverage is 52.02%.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #15732      +/-   ##
============================================
+ Coverage     66.33%   66.34%   +0.01%     
+ Complexity    31430    31429       -1     
============================================
  Files          3489     3489              
  Lines        130103   130091      -12     
  Branches      15201    15191      -10     
============================================
+ Hits          86302    86312      +10     
+ Misses        36718    36700      -18     
+ Partials       7083     7079       -4     
Flag Coverage Δ
integration 50.04% <41.21%> (+<0.01%) ⬆️
integration-h2 32.34% <25.67%> (-0.01%) ⬇️
unit 30.40% <37.16%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
...dhis/tracker/export/OperationsParamsValidator.java 66.66% <100.00%> (+4.76%) ⬆️
...ort/trackedentity/DefaultTrackedEntityService.java 84.39% <ø> (+19.30%) ⬆️
...export/trackedentity/TrackedEntityQueryParams.java 86.23% <100.00%> (ø)
...rackedentity/TrackedEntityRequestParamsMapper.java 79.43% <25.00%> (-19.30%) ⬇️
...ckedentity/TrackedEntityOperationParamsMapper.java 72.55% <57.62%> (-18.62%) ⬇️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a3ecb7a...30c26f4. Read the comment docs.

@Test
void shouldMapCorrectlyWhenTrackedEntityAndSpecificUpdatedRangeSupplied()
throws BadRequestException {
trackedEntityRequestParams.setOuMode(CAPTURE);

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note test

Invoking
TrackedEntityRequestParams.setOuMode
should be avoided because it has been deprecated.
@@ -66,22 +75,36 @@ class TrackedEntityOperationParamsMapper {

@Nonnull private final TrackedEntityAttributeService attributeService;

@Nonnull private final AclService aclService;

@Nonnull private final TrackedEntityStore trackedEntityStore;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having the store dependency in the mapper, but this is meant to be a temporary solution until we work on https://dhis2.atlassian.net/browse/DHIS2-15915

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you.
Can we at least add a comment or a TODO to it?

@muilpp muilpp marked this pull request as ready for review November 27, 2023 12:55
Copy link
Contributor

@enricocolasante enricocolasante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the comments are in fact on the old code that was just moved somewhere else but I think that now is the moment to clean up that code too.

The check removed for F_TRACKED_ENTITY_INSTANCE_SEARCH_IN_ALL_ORGUNITS seems a big deal to me, I am not sure if it is supposed to be in this PR

if (user == null
|| user.isSuper()
|| user.isAuthorized(F_TRACKED_ENTITY_INSTANCE_SEARCH_IN_ALL_ORGUNITS)) {
if (user == null || user.isSuper()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug fixing.
Should it be here in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can create a new ticket and add it there

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so because this probably needs to be backported, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's possible yes

Comment on lines 165 to 200
if (params.getProgram() == null) {
if (params.getProgramStatus() != null) {
violation = "Program must be defined when program status is defined";
}

if (params.getFollowUp() != null) {
violation = "Program must be defined when follow up status is defined";
}

if (params.getEnrollmentEnrolledAfter() != null) {
violation = "Program must be defined when program enrollment start date is specified";
}

if (params.getEnrollmentEnrolledBefore() != null) {
violation = "Program must be defined when program enrollment end date is specified";
}

if (params.getEnrollmentOccurredAfter() != null) {
violation = "Program must be defined when program incident start date is specified";
}

if (params.getEnrollmentOccurredBefore() != null) {
violation = "Program must be defined when program incident end date is specified";
}
}

if (params.getEventStatus() != null
&& (params.getEventOccurredAfter() == null || params.getEventOccurredBefore() == null)) {
violation = "Event start and end date must be specified when event status is specified";
}

if (params.getUpdatedWithin() != null
&& (params.getUpdatedAfter() != null || params.getUpdatedBefore() != null)) {
violation =
"Last updated from and/or to and last updated duration cannot be specified simultaneously";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you review the naming here?
For example, we don't have incident date anymore and last updated is just called updated now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reading the release notes, I see in the DB, the column name incidentdate is now called ocurreddate, but I don't see any mention to the parameters used in the payload. Is this explained somewhere else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters used in the payload didn't change so they are not in the release notes.
Here it is just a matter to check how things are called in the if statement and change the error message accordly.
I would even use a notation with back quotes to highlight the parameters that needs to be corrected

private List<TrackedEntityType> filterAndValidateTrackedEntityTypes(
User user, TrackedEntityType trackedEntityType, Program program) throws BadRequestException {
List<TrackedEntityType> trackedEntityTypes = new ArrayList<>();
if (trackedEntityType == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If program is not null, should we still define the trackedEntityTypes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the program is not null we still need to do it, but that's a good point, because we don't need to do it if a trackedEntityType is present in the request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Program is not null, shouldn't be somoething like this?
trackedEntityTypes = List.of(program.getTrackedEntityType)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way because it's how it's done before this refactor.
When no trackedEntityType is specified, it gets all the ones accessible by the user. Is there a reason why we should use the one of the program instead?

https://github.com/dhis2/dhis2-core/blob/master/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/trackedentity/DefaultTrackedEntityService.java#L472

@@ -66,22 +75,36 @@ class TrackedEntityOperationParamsMapper {

@Nonnull private final TrackedEntityAttributeService attributeService;

@Nonnull private final AclService aclService;

@Nonnull private final TrackedEntityStore trackedEntityStore;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you.
Can we at least add a comment or a TODO to it?

@muilpp muilpp marked this pull request as draft November 28, 2023 12:05
.toList();

if (program == null && trackedEntityTypes.isEmpty()) {
throw new BadRequestException("User has no access to any Tracked Entity Type");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is correct. Need to check if the query can return results if program is null and user has no access to any tet. In that case, the message will still be Either a program or tracked entity type must be specified

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TrackedEntity must have a TrackedEntityType even if it is not enforced at DB level.
So it is correct to stop the user to get a collection of TEs if it has no access to any TrackedEntityType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also possible to run a request if trackedEntities is present, also when program and trackedEntityType are null.

.toList();

if (program == null && trackedEntityTypes.isEmpty()) {
throw new BadRequestException("User has no access to any Tracked Entity Type");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TrackedEntity must have a TrackedEntityType even if it is not enforced at DB level.
So it is correct to stop the user to get a collection of TEs if it has no access to any TrackedEntityType


if (params.getProgram() != null && params.getTrackedEntityType() != null) {
throw new BadRequestException(
"Program and tracked entity cannot be specified simultaneously");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Program and tracked entity cannot be specified simultaneously");
"Program and tracked entity type cannot be specified simultaneously");

return emptyList();
}

private List<TrackedEntityType> filterAndValidateTrackedEntityTypes(User user) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think we should validate if user has no access to any Tracked Entity Types because if we skip this validation in the case the client specifies something in trackedEntities then we could return some results even if the user has no access to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we run the query against the DB we join the tracked entity type table and we filter by the value we found here. In case there's no value, the result of the query will be an empty list, so in theory we should not leak any information. But I agree that it's better to stop the request here instead of running a query against a database when we already know we'll return an empty list.

@muilpp muilpp marked this pull request as ready for review November 29, 2023 18:54
@muilpp muilpp enabled auto-merge (squash) November 29, 2023 18:54
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

@enricocolasante enricocolasante merged commit 20bb935 into master Nov 30, 2023
18 checks passed
@enricocolasante enricocolasante deleted the TECH-1656-refactor-tracked-entities-operation-layer branch November 30, 2023 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants