Skip to content

Conversation

@ameenhere
Copy link
Contributor

When registering a new TEI, there is no context of a program. But the old Tracker API goes ahead and checks program ownership for “all the programs in the system”. This itself was only part of the problem.

There is a cache used to store programOwnerOu for a TEI-Program combination. For TE creation, when the TE is yet to be created, it will not have an id (the primary key column which is obtained after a TE is persisted). So during the time of TE creation, the entity object will always have an id of 0. This 0 is used as part of the key to cache programOwners. This means any subsequent TEI registration will also have 0 as the id and will pick up the "wrong owner OU" from the cache(the first one that got into the cache).

So irrelevant access checks + incorrect cache hits was the root cause of the original issue.

This PR fixes this by making sure programOwnership is not checked when registering a new TEI and rather simply check if the user has TE.orgUnit in their Capture Scope and if the user has Data Write access to the TE.TEType

This fix is consistent with the new Tracker API, in master. In v42(master), when creating a new TE, it only checks if the user has dataWrite access to the TeType and nothing of ownership is checked. It has separate access check flows when the TE is being created vs when the TE is being updated.

@Override
@Transactional(readOnly = true)
public List<String> canCreate(User user, TrackedEntityInstance trackedEntity) {
if (user == null || user.isSuper() || trackedEntity == 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 I remember correctly, user was null when running internal jobs, but after the last refactor by the platform team, that's no longer the case

Copy link
Contributor

Choose a reason for hiding this comment

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

ah wait, this is for 2.39, so nevermind, probably good to have the null check

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR is for 2.39, and I also see other methods in the class doing the same user == null check.
Unsure if the refactor you talk about exists in 2.39.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah for some reason, your second comment just got in. Yes Agreed :D

Copy link
Contributor

Choose a reason for hiding this comment

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

That a null user means canCreate == true does not feel right. If we assume that the user must be authenticated when this is called we should not check for null. Better an NPE than unauthorized access

Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't feel right, but there's a job in previous versions run by a null user, which is why we need to skip this validation. However, it might be worth checking if that job needs to create a TE.

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 agree it does not feel right. Which is why in later versions, there was some work done to fix the user to never being null.
But in 2.39, I feel this may be safer. To follow the same approach as is done for canUpdate , canDelete, and canRead.


@Test
void shouldFailWhenRegisteringPersonOutsideCaptureScope() {

Copy link
Contributor

Choose a reason for hiding this comment

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

Usually, we don't include a line break between the method name and its code, and we format the test to follow the Arrange-Act-Assert pattern.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 3, 2024

@zubaira
Copy link
Contributor

zubaira commented Dec 3, 2024

When registering a new TEI, there is no context of a program. But the old Tracker API goes ahead and checks program ownership for “all the programs in the system”. This itself was only part of the problem.

There is a cache used to store programOwnerOu for a TEI-Program combination. For TE creation, when the TE is yet to be created, it will not have an id (the primary key column which is obtained after a TE is persisted). So during the time of TE creation, the entity object will always have an id of 0. This 0 is used as part of the key to cache programOwners. This means any subsequent TEI registration will also have 0 as the id and will pick up the "wrong owner OU" from the cache(the first one that got into the cache).

So irrelevant access checks + incorrect cache hits was the root cause of the original issue.

This PR fixes this by making sure programOwnership is not checked when registering a new TEI and rather simply check if the user has TE.orgUnit in their Capture Scope and if the user has Data Write access to the TE.TEType

This fix is consistent with the new Tracker API, in master. In v42(master), when creating a new TE, it only checks if the user has dataWrite access to the TeType and nothing of ownership is checked. It has separate access check flows when the TE is being created vs when the TE is being updated.

This is great, @ameenhere. Just for clarification:
Once the cache is polluted, will the error occur even if the same user tries to register another TEI, or does it only happen when a different user attempts to register a TEI?

@Override
@Transactional(readOnly = true)
public List<String> canCreate(User user, TrackedEntityInstance trackedEntity) {
if (user == null || user.isSuper() || trackedEntity == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

That a null user means canCreate == true does not feel right. If we assume that the user must be authenticated when this is called we should not check for null. Better an NPE than unauthorized access

@ameenhere
Copy link
Contributor Author

ameenhere commented Dec 4, 2024

Once the cache is polluted, will the error occur even if the same user tries to register another TEI, or does it only happen when a different user attempts to register a TEI?

@zubaira My assumption is, if the user was able to register one TEI, he will be able to register any number of TEIs (Since the cached entry of ownerOU falls into his scope). If the user was not able to register a TEI because of an incorrect ownerOU (polluted cache), he will not be able to register any TEIs until the polluted cache is cleared.

@ameenhere ameenhere merged commit 51e6591 into 2.39 Dec 4, 2024
@ameenhere ameenhere deleted the DHIS2-18551_2.39 branch December 4, 2024 08:56
ameenhere added a commit that referenced this pull request Dec 4, 2024
* fix: [DHIS2-18551] (2.39) Fix access check when registering TEI

* Apply spotless

* fix: Test setup of orgUnit Capture

* fix: Review comments
ameenhere added a commit that referenced this pull request Dec 4, 2024
…) (#19379)

* fix: [DHIS2-18551] (2.39) Fix access check when registering TEI

* Apply spotless

* fix: Test setup of orgUnit Capture

* fix: Review comments
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.

5 participants