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: Allow granting temporary access only when user in search scope [DHIS2-18784] #19670

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
4 changes: 0 additions & 4 deletions dhis-2/dhis-services/dhis-service-tracker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,6 @@
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>

<!-- Test -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Optional;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Hibernate;
import org.hisp.dhis.cache.Cache;
Expand All @@ -53,7 +54,6 @@
import org.hisp.dhis.user.CurrentUserUtil;
import org.hisp.dhis.user.UserDetails;
import org.hisp.dhis.user.UserService;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -149,72 +149,41 @@ public void transferOwnership(

@Override
@Transactional
public void assignOwnership(
TrackedEntity trackedEntity,
Program program,
OrganisationUnit organisationUnit,
boolean skipAccessValidation,
boolean overwriteIfExists) {
if (trackedEntity == null || program == null || organisationUnit == null) {
return;
public void grantTemporaryOwnership(
@Nonnull TrackedEntity trackedEntity, Program program, UserDetails user, String reason)
throws ForbiddenException {
if (canSkipOwnershipCheck(user, program)) {
throw new ForbiddenException(
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this break the API?
I feel it may be intrusive to throw an exception for the scenario were the check itself could be skipped? Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

The issue was that the check was used to skip the actual action that is granting temporary access.
In my opinion the API was just wrong because when the user was asking access for a not existent program, the API would return with a positive answer even though nothing happened.

"Temporary ownership not created. Either current user is a superuser, program supplied does not exist or program supplied is not a tracker program.");
}

UserDetails currentUser = CurrentUserUtil.getCurrentUserDetails();

if (hasAccess(currentUser, trackedEntity, program) || skipAccessValidation) {
TrackedEntityProgramOwner teProgramOwner =
trackedEntityProgramOwnerService.getTrackedEntityProgramOwner(trackedEntity, program);

if (teProgramOwner != null) {
if (overwriteIfExists && !teProgramOwner.getOrganisationUnit().equals(organisationUnit)) {
ProgramOwnershipHistory programOwnershipHistory =
new ProgramOwnershipHistory(
program,
trackedEntity,
teProgramOwner.getOrganisationUnit(),
teProgramOwner.getLastUpdated(),
teProgramOwner.getCreatedBy());
programOwnershipHistoryService.addProgramOwnershipHistory(programOwnershipHistory);
trackedEntityProgramOwnerService.updateTrackedEntityProgramOwner(
trackedEntity, program, organisationUnit);
}
} else {
trackedEntityProgramOwnerService.createTrackedEntityProgramOwner(
trackedEntity, program, organisationUnit);
}

ownerCache.invalidate(getOwnershipCacheKey(trackedEntity::getId, program));
} else {
log.error("Unauthorized attempt to assign ownership");
throw new AccessDeniedException(
"User does not have access to assign ownership for the entity-program combination");
if (!program.isProtected()) {
throw new ForbiddenException(
String.format(
"Temporary ownership can only be granted to protected programs. %s access level is %s.",
program.getUid(), program.getAccessLevel().name()));
}
}

@Override
@Transactional
public void grantTemporaryOwnership(
TrackedEntity trackedEntity, Program program, UserDetails user, String reason) {
if (canSkipOwnershipCheck(user, program) || trackedEntity == null) {
return;
if (!isOwnerInUserSearchScope(user, trackedEntity, program)) {
throw new ForbiddenException(
"The owner of the entity-program combination is not in the user's search scope.");
}

if (program.isProtected()) {
if (config.isEnabled(CHANGELOG_TRACKER)) {
programTempOwnershipAuditService.addProgramTempOwnershipAudit(
new ProgramTempOwnershipAudit(program, trackedEntity, reason, user.getUsername()));
}
ProgramTempOwner programTempOwner =
new ProgramTempOwner(
program,
trackedEntity,
reason,
userService.getUser(user.getUid()),
TEMPORARY_OWNERSHIP_VALIDITY_IN_HOURS);
programTempOwnerService.addProgramTempOwner(programTempOwner);
tempOwnerCache.invalidate(
getTempOwnershipCacheKey(trackedEntity.getUid(), program.getUid(), user.getUid()));
if (config.isEnabled(CHANGELOG_TRACKER)) {
programTempOwnershipAuditService.addProgramTempOwnershipAudit(
new ProgramTempOwnershipAudit(program, trackedEntity, reason, user.getUsername()));
}

ProgramTempOwner programTempOwner =
new ProgramTempOwner(
program,
trackedEntity,
reason,
userService.getUser(user.getUid()),
TEMPORARY_OWNERSHIP_VALIDITY_IN_HOURS);
programTempOwnerService.addProgramTempOwner(programTempOwner);
tempOwnerCache.invalidate(
getTempOwnershipCacheKey(trackedEntity.getUid(), program.getUid(), user.getUid()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,6 @@ public interface TrackerOwnershipManager {
void transferOwnership(TrackedEntity trackedEntity, Program program, OrganisationUnit orgUnit)
throws ForbiddenException;

/**
* @param trackedEntity The tracked entity object
* @param program The program object
* @param organisationUnit The org unit that has to become the owner
*/
void assignOwnership(
TrackedEntity trackedEntity,
Program program,
OrganisationUnit organisationUnit,
boolean skipAccessValidation,
boolean overwriteIfExists);

/**
* Check whether the user has access (as owner or has temporarily broken the glass) to the tracked
* entity - program combination.
Expand All @@ -87,7 +75,8 @@ boolean hasAccess(
* @param reason The reason for requesting temporary ownership
*/
void grantTemporaryOwnership(
TrackedEntity trackedEntity, Program program, UserDetails user, String reason);
TrackedEntity trackedEntity, Program program, UserDetails user, String reason)
throws ForbiddenException;

/**
* Ownership check can be skipped if the user is superuser or if the program type is without
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.hisp.dhis.program.ProgramType;
import org.hisp.dhis.security.acl.AccessStringHelper;
import org.hisp.dhis.test.integration.PostgresIntegrationTestBase;
import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService;
import org.hisp.dhis.tracker.acl.TrackerAccessManager;
import org.hisp.dhis.tracker.acl.TrackerOwnershipManager;
import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityService;
Expand All @@ -82,6 +83,8 @@ class TrackerAccessManagerTest extends PostgresIntegrationTestBase {

@Autowired private TrackerOwnershipManager trackerOwnershipManager;

@Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService;

@Autowired private TrackedEntityTypeService trackedEntityTypeService;

@Autowired private TrackedEntityService trackedEntityService;
Expand Down Expand Up @@ -271,7 +274,8 @@ void checkAccessPermissionForEnrollmentInClosedProgram() throws ForbiddenExcepti
"User has no create access to organisation unit:");
enrollment.setOrganisationUnit(orgUnitA);
// Transferring ownership to orgUnitB. user is no longer owner
trackerOwnershipManager.assignOwnership(trackedEntity, programA, orgUnitA, false, true);
trackedEntityProgramOwnerService.createTrackedEntityProgramOwner(
trackedEntity, programA, orgUnitA);
trackerOwnershipManager.transferOwnership(trackedEntity, programA, orgUnitB);
// Cannot create enrollment if not owner
assertHasError(
Expand Down Expand Up @@ -388,7 +392,8 @@ void checkAccessPermissionsForEventInClosedProgram() throws ForbiddenException {
assertNoErrors(trackerAccessManager.canUpdate(userDetails, eventB, false));
// Can delete events if user is owner irrespective of eventOU
assertNoErrors(trackerAccessManager.canDelete(userDetails, eventB, false));
trackerOwnershipManager.assignOwnership(trackedEntityA, programA, orgUnitA, false, true);
trackedEntityProgramOwnerService.createTrackedEntityProgramOwner(
trackedEntityA, programA, orgUnitA);
trackerOwnershipManager.transferOwnership(trackedEntityA, programA, orgUnitB);
// Cannot create events anywhere if user is not owner
assertHasErrors(2, trackerAccessManager.canCreate(userDetails, eventB, false));
Expand Down
Loading
Loading