From 6e8d1f7d7474401257751f8cdffec5348cad01ae Mon Sep 17 00:00:00 2001 From: marc Date: Sat, 18 Jan 2025 20:00:31 +0100 Subject: [PATCH] fix: Allow granting temporary access only when user in search scope [DHIS2-18784] (#19670) * fix: Allow temp access only when user in search scope [DHIS2-18784] * fix: Rephrase message in exception [DHIS2-18784] * fix: Remove unused spring dependency [DHIS2-18784] * fix: Add dot at the end of the sentence [DHIS2-18784] * fix: Refactor to improve readability [DHIS2-18784] * fix: Skip getting TE enrollments [DHIS2-18784] * Update dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/DefaultTrackerOwnershipManager.java Co-authored-by: Enrico Colasante * fix: Use regular user on controller test [DHIS2-18784] * fix: Fix error message in tests [DHIS2-18784] * fix: Split param validation [DHIS2-18784] --------- Co-authored-by: Enrico Colasante --- .../dhis-service-tracker/pom.xml | 4 - .../acl/DefaultTrackerOwnershipManager.java | 97 ++++------ .../tracker/acl/TrackerOwnershipManager.java | 15 +- .../TrackerAccessManagerTest.java | 9 +- .../TrackerOwnershipManagerTest.java | 182 ++++++++++++------ .../relationship/RelationshipServiceTest.java | 7 +- .../TrackedEntityServiceTest.java | 29 +-- .../EventSecurityImportValidationTest.java | 3 +- .../TrackerOwnershipControllerTest.java | 35 +++- .../ownership/TrackerOwnershipController.java | 6 +- 10 files changed, 222 insertions(+), 165 deletions(-) diff --git a/dhis-2/dhis-services/dhis-service-tracker/pom.xml b/dhis-2/dhis-services/dhis-service-tracker/pom.xml index e4067be79ef4..80f3cf952c24 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/pom.xml +++ b/dhis-2/dhis-services/dhis-service-tracker/pom.xml @@ -167,10 +167,6 @@ org.geotools gt-main - - org.springframework.security - spring-security-core - diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/DefaultTrackerOwnershipManager.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/DefaultTrackerOwnershipManager.java index 068d619ce5d0..12f50fbf7687 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/DefaultTrackerOwnershipManager.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/DefaultTrackerOwnershipManager.java @@ -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; @@ -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; @@ -149,71 +149,54 @@ 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 { + validateGrantTemporaryOwnershipInputs(trackedEntity, program, user); + + if (config.isEnabled(CHANGELOG_TRACKER)) { + programTempOwnershipAuditService.addProgramTempOwnershipAudit( + new ProgramTempOwnershipAudit(program, trackedEntity, reason, user.getUsername())); } - UserDetails currentUser = CurrentUserUtil.getCurrentUserDetails(); + 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 (hasAccess(currentUser, trackedEntity, program) || skipAccessValidation) { - TrackedEntityProgramOwner teProgramOwner = - trackedEntityProgramOwnerService.getTrackedEntityProgramOwner(trackedEntity, program); + private void validateGrantTemporaryOwnershipInputs( + TrackedEntity trackedEntity, Program program, UserDetails user) throws ForbiddenException { + if (program == null) { + throw new ForbiddenException( + "Temporary ownership not created. Program supplied does not exist."); + } - 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); - } + if (user.isSuper()) { + throw new ForbiddenException("Temporary ownership not created. Current user is a superuser."); + } - 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 (ProgramType.WITHOUT_REGISTRATION == program.getProgramType()) { + throw new ForbiddenException( + "Temporary ownership not created. Program supplied is not a tracker program."); } - } - @Override - @Transactional - public void grantTemporaryOwnership( - TrackedEntity trackedEntity, Program program, UserDetails user, String reason) { - if (canSkipOwnershipCheck(user, program) || trackedEntity == null) { - return; + 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())); } - 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 (!isOwnerInUserSearchScope(user, trackedEntity, program)) { + throw new ForbiddenException( + "The owner of the entity-program combination is not in the user's search scope."); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/TrackerOwnershipManager.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/TrackerOwnershipManager.java index bd1a930bbfb7..ac0a7ac65c16 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/TrackerOwnershipManager.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/acl/TrackerOwnershipManager.java @@ -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. @@ -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 diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java index 4fb09bcbeb84..ba633bdbfbd7 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java @@ -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; @@ -82,6 +83,8 @@ class TrackerAccessManagerTest extends PostgresIntegrationTestBase { @Autowired private TrackerOwnershipManager trackerOwnershipManager; + @Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService; + @Autowired private TrackedEntityTypeService trackedEntityTypeService; @Autowired private TrackedEntityService trackedEntityService; @@ -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( @@ -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)); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerOwnershipManagerTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerOwnershipManagerTest.java index c85e2dcacd0b..6440cb149a0d 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerOwnershipManagerTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerOwnershipManagerTest.java @@ -27,7 +27,12 @@ */ package org.hisp.dhis.trackedentity; +import static org.hisp.dhis.common.AccessLevel.AUDITED; +import static org.hisp.dhis.common.AccessLevel.CLOSED; +import static org.hisp.dhis.common.AccessLevel.OPEN; +import static org.hisp.dhis.common.AccessLevel.PROTECTED; import static org.hisp.dhis.common.OrganisationUnitSelectionMode.ACCESSIBLE; +import static org.hisp.dhis.test.utils.Assertions.assertContains; import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; import static org.hisp.dhis.tracker.TrackerTestUtils.uids; @@ -39,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import org.hisp.dhis.common.AccessLevel; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; @@ -50,9 +56,11 @@ import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; +import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.security.Authorities; 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.TrackerOwnershipManager; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityEnrollmentParams; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityOperationParams; @@ -61,8 +69,11 @@ import org.hisp.dhis.user.UserDetails; import org.hisp.dhis.user.sharing.Sharing; import org.hisp.dhis.user.sharing.UserAccess; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; /** @@ -83,6 +94,8 @@ class TrackerOwnershipManagerTest extends PostgresIntegrationTestBase { @Autowired private TrackedEntityTypeService trackedEntityTypeService; + @Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService; + private TrackedEntity trackedEntityA1; private TrackedEntity trackedEntityB1; @@ -133,7 +146,7 @@ void setUp() { createAndAddUserWithAuth("trackertestownership", organisationUnitA, Authorities.ALL); programA = createProgram('A'); - programA.setAccessLevel(AccessLevel.PROTECTED); + programA.setAccessLevel(PROTECTED); programA.setTrackedEntityType(trackedEntityType); programA.setOrganisationUnits(Set.of(organisationUnitA, organisationUnitB)); programService.addProgram(programA); @@ -162,25 +175,19 @@ void setUp() { } @Test - void testAssignOwnership() { + void shouldFailWhenGrantingTemporaryOwnershipAndUserNotInSearchScope() { assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsA, trackedEntityA1, programA)); assertFalse(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programA)); - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityB1, programA)); - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitB, false, true); - assertFalse(trackerOwnershipAccessManager.hasAccess(userDetailsA, trackedEntityA1, programA)); - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programA)); - } + Exception exception = + Assertions.assertThrows( + ForbiddenException.class, + () -> + trackerOwnershipAccessManager.grantTemporaryOwnership( + trackedEntityA1, programA, userDetailsB, "testing reason")); - @Test - void testGrantTemporaryOwnershipWithAudit() { - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsA, trackedEntityA1, programA)); - assertFalse(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programA)); - trackerOwnershipAccessManager.grantTemporaryOwnership( - trackedEntityA1, programA, userDetailsB, "testing reason"); - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsA, trackedEntityA1, programA)); - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsA, trackedEntityA1, programA)); - assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programA)); + assertEquals( + "The owner of the entity-program combination is not in the user's search scope.", + exception.getMessage()); } @Test @@ -188,8 +195,8 @@ void shouldNotHaveAccessToEnrollmentWithUserAWhenTransferredToAnotherOrgUnit() throws ForbiddenException { userA.setTeiSearchOrganisationUnits(Set.of(organisationUnitB)); userService.updateUser(userA); - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityA1, programA, organisationUnitB); injectSecurityContextUser(userA); @@ -205,8 +212,8 @@ void shouldNotHaveAccessToEnrollmentWithUserAWhenTransferredToAnotherOrgUnit() @Test void shouldHaveAccessToEnrollmentWithUserBWhenTransferredToOwnOrgUnit() throws ForbiddenException, NotFoundException, BadRequestException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityA1, programA, organisationUnitB); injectSecurityContextUser(userB); @@ -219,8 +226,8 @@ void shouldHaveAccessToEnrollmentWithUserBWhenTransferredToOwnOrgUnit() @Test void shouldHaveAccessToEnrollmentWithSuperUserWhenTransferredToOwnOrgUnit() throws ForbiddenException, NotFoundException, BadRequestException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityA1, programA, organisationUnitB); superUser.setOrganisationUnits(Set.of(organisationUnitB)); userService.updateUser(superUser); @@ -245,8 +252,8 @@ void shouldHaveAccessToTEWhenProgramNotProvidedButUserHasAccessToAtLeastOneProgr @Test void shouldNotHaveAccessToTEWhenProgramNotProvidedAndUserHasNoAccessToAnyProgram() { injectSecurityContextUser(userA); - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitB, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitB); ForbiddenException exception = assertThrows( @@ -272,9 +279,14 @@ void shouldHaveAccessWhenProgramProtectedAndUserInCaptureScope() { } @Test - void shouldHaveAccessWhenProgramProtectedAndHasTemporaryAccess() { + void shouldHaveAccessWhenProgramProtectedAndHasTemporaryAccess() throws ForbiddenException { + userB.setTeiSearchOrganisationUnits(Set.of(organisationUnitA)); + userService.updateUser(userB); + userDetailsB = UserDetails.fromUser(userB); + trackerOwnershipAccessManager.grantTemporaryOwnership( trackedEntityA1, programA, userDetailsB, "test protected program"); + assertTrue(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programA)); assertTrue( trackerOwnershipAccessManager.hasAccess( @@ -338,32 +350,76 @@ void shouldHaveAccessWhenProgramClosedAndUserInCaptureScope() { programB)); } + private static Stream providePrograms() { + return Stream.of(createProgram(OPEN), createProgram(AUDITED), createProgram(CLOSED)); + } + + @ParameterizedTest + @MethodSource("providePrograms") + void shouldFailWhenGrantingTemporaryOwnershipToProgramWithAccessLevelOtherThanProtected( + Program program) { + Exception exception = + Assertions.assertThrows( + ForbiddenException.class, + () -> + trackerOwnershipAccessManager.grantTemporaryOwnership( + trackedEntityA1, program, userDetailsB, "test temporary ownership")); + + assertContains( + "Temporary ownership can only be granted to protected programs.", exception.getMessage()); + } + @Test - void shouldNotHaveAccessWhenProgramClosedAndUserHasTemporaryAccess() { - trackerOwnershipAccessManager.grantTemporaryOwnership( - trackedEntityA1, programB, userDetailsB, "test closed program"); - assertFalse(trackerOwnershipAccessManager.hasAccess(userDetailsB, trackedEntityA1, programB)); - assertFalse( - trackerOwnershipAccessManager.hasAccess( - UserDetails.fromUser(userB), - trackedEntityA1.getUid(), - trackedEntityA1.getOrganisationUnit(), - programB)); + void shouldFailWhenGrantingTemporaryAccessIfUserIsSuperuser() { + Exception exception = + Assertions.assertThrows( + ForbiddenException.class, + () -> + trackerOwnershipAccessManager.grantTemporaryOwnership( + trackedEntityA1, + programA, + UserDetails.fromUser(superUser), + "test temporary ownership")); - injectSecurityContextUser(userB); - ForbiddenException exception = - assertThrows( + assertEquals( + "Temporary ownership not created. Current user is a superuser.", exception.getMessage()); + } + + @Test + void shouldFailWhenGrantingTemporaryAccessIfProgramIsNull() { + Exception exception = + Assertions.assertThrows( ForbiddenException.class, () -> - trackedEntityService.getTrackedEntity( - UID.of(trackedEntityA1), UID.of(programB), defaultParams)); - assertEquals(TrackerOwnershipManager.PROGRAM_ACCESS_CLOSED, exception.getMessage()); + trackerOwnershipAccessManager.grantTemporaryOwnership( + trackedEntityA1, null, userDetailsB, "test temporary ownership")); + + assertEquals( + "Temporary ownership not created. Program supplied does not exist.", + exception.getMessage()); + } + + @Test + void shouldFailWhenGrantingTemporaryAccessIfProgramIsNotTrackerProgram() { + Program eventProgram = createProgram(PROTECTED); + eventProgram.setProgramType(ProgramType.WITHOUT_REGISTRATION); + + Exception exception = + Assertions.assertThrows( + ForbiddenException.class, + () -> + trackerOwnershipAccessManager.grantTemporaryOwnership( + trackedEntityA1, eventProgram, userDetailsB, "test temporary ownership")); + + assertEquals( + "Temporary ownership not created. Program supplied is not a tracker program.", + exception.getMessage()); } @Test void shouldHaveAccessWhenProgramOpenAndUserInScope() throws ForbiddenException, NotFoundException, BadRequestException { - programA.setAccessLevel(AccessLevel.OPEN); + programA.setAccessLevel(OPEN); programService.updateProgram(programA); assertEquals( @@ -374,11 +430,11 @@ void shouldHaveAccessWhenProgramOpenAndUserInScope() @Test void shouldNotHaveAccessWhenProgramOpenAndUserNotInSearchScope() throws ForbiddenException { - programA.setAccessLevel(AccessLevel.OPEN); + programA.setAccessLevel(OPEN); programService.updateProgram(programA); - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityA1, programA, organisationUnitB); injectSecurityContextUser(userA); @@ -394,8 +450,8 @@ void shouldNotHaveAccessWhenProgramOpenAndUserNotInSearchScope() throws Forbidde @Test void shouldHaveAccessWhenProgramNotProvidedAndTEEnrolledButHaveAccessToTEOwner() throws ForbiddenException, NotFoundException, BadRequestException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityA1, programA, organisationUnitB); injectSecurityContextUser(userB); @@ -447,8 +503,8 @@ void shouldNotHaveAccessWhenProgramNotProvidedAndTENotEnrolledAndNoAccessToTeReg @Test void shouldFindTrackedEntityWhenTransferredToAccessibleOrgUnit() throws ForbiddenException, BadRequestException, NotFoundException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); transferOwnership(trackedEntityA1, programA, organisationUnitB); TrackedEntityOperationParams operationParams = createOperationParams(null); injectSecurityContext(userDetailsB); @@ -461,8 +517,8 @@ void shouldFindTrackedEntityWhenTransferredToAccessibleOrgUnit() @Test void shouldFindTrackedEntityWhenTransferredToAccessibleOrgUnitAndSuperUser() throws ForbiddenException, BadRequestException, NotFoundException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); transferOwnership(trackedEntityA1, programA, organisationUnitB); superUser.setOrganisationUnits(Set.of(organisationUnitB)); userService.updateUser(superUser); @@ -477,8 +533,8 @@ void shouldFindTrackedEntityWhenTransferredToAccessibleOrgUnitAndSuperUser() @Test void shouldNotFindTrackedEntityWhenTransferredToInaccessibleOrgUnit() throws ForbiddenException, BadRequestException, NotFoundException { - trackerOwnershipAccessManager.assignOwnership( - trackedEntityA1, programA, organisationUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); transferOwnership(trackedEntityA1, programA, organisationUnitB); TrackedEntityOperationParams operationParams = createOperationParams(null); @@ -531,7 +587,8 @@ void shouldNotTransferOwnershipWhenOrgUnitNotAssociatedToProgram() { @Test void shouldFindTrackedEntityWhenProgramSuppliedAndUserIsOwner() throws ForbiddenException, BadRequestException, NotFoundException { - assignOwnership(trackedEntityA1, programA, organisationUnitA); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); TrackedEntityOperationParams operationParams = createOperationParams(UID.of(programA)); injectSecurityContext(userDetailsA); @@ -543,7 +600,8 @@ void shouldFindTrackedEntityWhenProgramSuppliedAndUserIsOwner() @Test void shouldNotFindTrackedEntityWhenProgramSuppliedAndUserIsNotOwner() throws ForbiddenException, BadRequestException, NotFoundException { - assignOwnership(trackedEntityA1, programA, organisationUnitA); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA1, programA, organisationUnitA); TrackedEntityOperationParams operationParams = createOperationParams(UID.of(programA)); injectSecurityContext(userDetailsB); @@ -556,11 +614,6 @@ private void transferOwnership( trackerOwnershipAccessManager.transferOwnership(trackedEntity, program, orgUnit); } - private void assignOwnership( - TrackedEntity trackedEntity, Program program, OrganisationUnit orgUnit) { - trackerOwnershipAccessManager.assignOwnership(trackedEntity, program, orgUnit, false, true); - } - private TrackedEntityOperationParams createOperationParams(UID programUid) { return TrackedEntityOperationParams.builder() .trackedEntityType(trackedEntityA1.getTrackedEntityType()) @@ -573,4 +626,11 @@ private List getTrackedEntities(TrackedEntityOperationParams params) throws ForbiddenException, BadRequestException, NotFoundException { return uids(trackedEntityService.getTrackedEntities(params)); } + + private static Program createProgram(AccessLevel accessLevel) { + Program program = new Program(); + program.setAccessLevel(accessLevel); + + return program; + } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/relationship/RelationshipServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/relationship/RelationshipServiceTest.java index 07af5aace287..7c470ced7901 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/relationship/RelationshipServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/relationship/RelationshipServiceTest.java @@ -58,6 +58,7 @@ import org.hisp.dhis.test.utils.RelationshipUtils; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService; import org.hisp.dhis.tracker.acl.TrackerOwnershipManager; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserService; @@ -79,6 +80,8 @@ class RelationshipServiceTest extends PostgresIntegrationTestBase { @Autowired private TrackerOwnershipManager trackerOwnershipAccessManager; + @Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService; + private Date enrollmentDate; private TrackedEntity teA; @@ -301,8 +304,8 @@ void shouldNotReturnRelationshipWhenTeIsTransferredAndUserHasNoAccessToAtLeastOn manager.save(createEnrollment(program, trackedEntityFrom, orgUnitA)); - trackerOwnershipAccessManager.assignOwnership( - trackedEntityFrom, program, orgUnitA, false, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityFrom, program, orgUnitA); trackerOwnershipAccessManager.transferOwnership(trackedEntityFrom, program, orgUnitB); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java index 6e99d46da1c8..c02670ca455e 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java @@ -100,7 +100,7 @@ import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeAttribute; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.acl.TrackerOwnershipManager; +import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityOperationParams.TrackedEntityOperationParamsBuilder; import org.hisp.dhis.tracker.trackedentityattributevalue.TrackedEntityAttributeValueService; import org.hisp.dhis.user.User; @@ -123,7 +123,7 @@ class TrackedEntityServiceTest extends PostgresIntegrationTestBase { @Autowired private TrackedEntityAttributeValueService attributeValueService; - @Autowired private TrackerOwnershipManager trackerOwnershipManager; + @Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService; private User user; @@ -402,8 +402,10 @@ void setUp() { trackedEntityC.setTrackedEntityType(trackedEntityTypeA); manager.save(trackedEntityC, false); - trackerOwnershipManager.assignOwnership(trackedEntityA, programA, orgUnitA, true, true); - trackerOwnershipManager.assignOwnership(trackedEntityA, programB, orgUnitA, true, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA, programA, orgUnitA); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityA, programB, orgUnitA); attributeValueService.addTrackedEntityAttributeValue( new TrackedEntityAttributeValue(teaA, trackedEntityA, "A")); @@ -595,7 +597,8 @@ void shouldReturnTrackedEntitiesGivenUserHasDataReadAccessToTrackedEntityType() void shouldReturnTrackedEntityIncludingAllAttributesEnrollmentsEventsRelationshipsOwners() throws ForbiddenException, NotFoundException, BadRequestException { // this was declared as "remove ownership"; unclear to me how this is removing ownership - trackerOwnershipManager.assignOwnership(trackedEntityA, programB, orgUnitB, true, true); + trackedEntityProgramOwnerService.updateTrackedEntityProgramOwner( + trackedEntityA, programB, orgUnitB); TrackedEntityOperationParams operationParams = TrackedEntityOperationParams.builder() @@ -1532,8 +1535,8 @@ void shouldNotReturnTrackedEntityRelationshipWhenTEFromItemNotAccessible() manager.save(inaccessibleOrgUnit); makeProgramMetadataInaccessible(programB); makeProgramMetadataInaccessible(programC); - trackerOwnershipManager.assignOwnership( - trackedEntityA, programA, inaccessibleOrgUnit, true, true); + trackedEntityProgramOwnerService.updateTrackedEntityProgramOwner( + trackedEntityA, programA, inaccessibleOrgUnit); TrackedEntityOperationParams operationParams = createOperationParams(orgUnitB, trackedEntityB); injectSecurityContextUser(user); @@ -1555,8 +1558,8 @@ void shouldNotReturnTrackedEntityRelationshipWhenTEToItemNotAccessible() manager.save(inaccessibleOrgUnit); makeProgramMetadataInaccessible(programB); makeProgramMetadataInaccessible(programC); - trackerOwnershipManager.assignOwnership( - trackedEntityB, programA, inaccessibleOrgUnit, true, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityB, programA, inaccessibleOrgUnit); TrackedEntityOperationParams operationParams = createOperationParams(orgUnitA, trackedEntityA); injectSecurityContextUser(user); @@ -1595,8 +1598,8 @@ void shouldNotReturnTrackedEntityRelationshipWhenEnrollmentItemNotAccessible() OrganisationUnit inaccessibleOrgUnit = createOrganisationUnit('D'); manager.save(inaccessibleOrgUnit); makeProgramMetadataInaccessible(programC); - trackerOwnershipManager.assignOwnership( - trackedEntityB, programB, inaccessibleOrgUnit, true, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityB, programB, inaccessibleOrgUnit); TrackedEntityOperationParams operationParams = createOperationParams(orgUnitA, trackedEntityA); injectSecurityContextUser(user); @@ -1635,8 +1638,8 @@ void shouldReturnTrackedEntityRelationshipWhenEventItemNotAccessible() OrganisationUnit inaccessibleOrgUnit = createOrganisationUnit('D'); manager.save(inaccessibleOrgUnit); makeProgramMetadataInaccessible(programC); - trackerOwnershipManager.assignOwnership( - trackedEntityB, programB, inaccessibleOrgUnit, true, true); + trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( + trackedEntityB, programB, inaccessibleOrgUnit); TrackedEntityOperationParams operationParams = createOperationParams(orgUnitA, trackedEntityA); injectSecurityContextUser(user); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventSecurityImportValidationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventSecurityImportValidationTest.java index 76695043d1dd..e649f3633fb1 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventSecurityImportValidationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventSecurityImportValidationTest.java @@ -204,7 +204,8 @@ void setUp() throws IOException { manager.save(enrollmentA); maleA.getEnrollments().add(enrollmentA); manager.update(maleA); - trackerOwnershipAccessManager.assignOwnership(maleA, programA, organisationUnitA, false, false); + trackedEntityProgramOwnerService.updateTrackedEntityProgramOwner( + maleA, programA, organisationUnitA); trackedEntityProgramOwnerService.updateTrackedEntityProgramOwner( maleA, programA, organisationUnitA); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipControllerTest.java index ad99714e7c4b..db48f507bc2c 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipControllerTest.java @@ -57,6 +57,8 @@ class TrackerOwnershipControllerTest extends PostgresControllerIntegrationTestBa private String pId; + private User regularUser; + @BeforeEach void setUp() { orgUnitAUid = @@ -74,11 +76,19 @@ void setUp() { OrganisationUnit orgUnitA = manager.get(OrganisationUnit.class, orgUnitAUid); OrganisationUnit orgUnitB = manager.get(OrganisationUnit.class, orgUnitBUid); - User user = - createAndAddUser(true, "user", Set.of(orgUnitA, orgUnitB), Set.of(orgUnitA, orgUnitB)); - injectSecurityContextUser(user); - - String tetId = assertStatus(HttpStatus.CREATED, POST("/trackedEntityTypes/", "{'name': 'A'}")); + regularUser = + createAndAddUser( + false, "regular-user", Set.of(orgUnitA, orgUnitB), Set.of(orgUnitA, orgUnitB)); + User superuser = + createAndAddUser(true, "superuser", Set.of(orgUnitA, orgUnitB), Set.of(orgUnitA, orgUnitB)); + injectSecurityContextUser(superuser); + + String tetId = + assertStatus( + HttpStatus.CREATED, + POST( + "/trackedEntityTypes/", + "{'name': 'A', 'sharing':{'external':false,'public':'rwrw----'}}")); teUid = CodeGenerator.generateUid(); assertStatus( @@ -107,11 +117,14 @@ void setUp() { { 'name':'P1', 'shortName':'P1', - 'programType':'WITHOUT_REGISTRATION', - 'organisationUnits': [{'id':'%s'},{'id':'%s'}] + 'programType':'WITH_REGISTRATION', + 'accessLevel':'PROTECTED', + 'trackedEntityType': {'id': '%s'}, + 'organisationUnits': [{'id':'%s'},{'id':'%s'}], + 'sharing':{'external':false,'public':'rwrw----'} } """ - .formatted(orgUnitAUid, orgUnitBUid))); + .formatted(tetId, orgUnitAUid, orgUnitBUid))); } @Test @@ -170,7 +183,8 @@ void shouldFailToUpdateWhenNoTrackedEntityOrTrackedEntityInstanceParametersArePr } @Test - void shouldOverrideOwnershipAccessWhenUsingDeprecateTrackedEntityInstanceParam() { + void shouldGrantTemporaryAccessWhenUsingDeprecateTrackedEntityInstanceParam() { + injectSecurityContextUser(regularUser); assertWebMessage( "OK", 200, @@ -184,7 +198,8 @@ void shouldOverrideOwnershipAccessWhenUsingDeprecateTrackedEntityInstanceParam() } @Test - void shouldOverrideOwnershipAccess() { + void shouldGrantTemporaryAccess() { + injectSecurityContextUser(regularUser); assertWebMessage( "OK", 200, diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipController.java index 1a4d3d5fdd2d..8fea04d2b93a 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/ownership/TrackerOwnershipController.java @@ -43,6 +43,7 @@ import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; import org.hisp.dhis.tracker.acl.TrackerOwnershipManager; +import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityParams; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityService; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.UserDetails; @@ -97,7 +98,8 @@ public WebMessage updateTrackerProgramOwner( "trackedEntityInstance", trackedEntityInstance, "trackedEntity", trackedEntity); trackerOwnershipAccessManager.transferOwnership( - trackedEntityService.getTrackedEntity(trackedEntityUid), + trackedEntityService.getTrackedEntity( + trackedEntityUid, UID.of(program), TrackedEntityParams.FALSE), programService.getProgram(program), organisationUnitService.getOrganisationUnit(ou)); return ok("Ownership transferred"); @@ -105,7 +107,7 @@ public WebMessage updateTrackerProgramOwner( @PostMapping(value = "/override", produces = APPLICATION_JSON_VALUE) @ResponseBody - public WebMessage overrideOwnershipAccess( + public WebMessage grantTemporaryAccess( @Deprecated(since = "2.41") @RequestParam(required = false) UID trackedEntityInstance, @RequestParam(required = false) UID trackedEntity, @RequestParam String reason,