diff --git a/ProcessMaker/Http/Controllers/RequestController.php b/ProcessMaker/Http/Controllers/RequestController.php index 37723a2918..c54e60b5a5 100644 --- a/ProcessMaker/Http/Controllers/RequestController.php +++ b/ProcessMaker/Http/Controllers/RequestController.php @@ -200,7 +200,7 @@ public function show(ProcessRequest $request, Media $mediaItems) $addons = $this->getPluginAddons('edit', compact(['request'])); $dataActionsAddons = $this->getPluginAddons('edit.dataActions', []); - $isProcessManager = $request->process?->manager_id === Auth::user()->id; + $isProcessManager = in_array(Auth::user()->id, $request->process?->manager_id ?? []); $eligibleRollbackTask = null; $errorTask = RollbackProcessRequest::getErrorTask($request); diff --git a/ProcessMaker/Policies/ProcessRequestPolicy.php b/ProcessMaker/Policies/ProcessRequestPolicy.php index 7d42766194..508523ee6f 100644 --- a/ProcessMaker/Policies/ProcessRequestPolicy.php +++ b/ProcessMaker/Policies/ProcessRequestPolicy.php @@ -33,6 +33,10 @@ public function before(User $user) */ public function view(User $user, ProcessRequest $processRequest) { + if (in_array($user->id, $processRequest->process?->manager_id ?? [])) { + return true; + } + // Policy defined in ForUserScope return ProcessRequest::forUser($user) ->where('process_requests.id', $processRequest->id) diff --git a/tests/Feature/Api/ProcessRequestPolicyTest.php b/tests/Feature/Api/ProcessRequestPolicyTest.php index 708b381d8e..f801f54cba 100644 --- a/tests/Feature/Api/ProcessRequestPolicyTest.php +++ b/tests/Feature/Api/ProcessRequestPolicyTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Api; use Database\Seeders\PermissionSeeder; +use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; use ProcessMaker\Models\User; @@ -72,4 +73,203 @@ public function testUserHasPermission() $response = $this->apiCall('GET', $route); $response->assertStatus(200); } + + public function testProcessManagerCanViewManagedRequestAndEndEventDestination() + { + $firstManager = User::factory()->create(); + $process = Process::factory()->create([ + 'bpmn' => file_get_contents(__DIR__ . '/../../Fixtures/action_by_email_process_no_require_login.bpmn'), + 'manager_id' => [$firstManager->id, $this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + 'status' => 'COMPLETED', + ]); + ProcessRequestToken::factory()->create([ + 'element_id' => 'node_18', + 'element_type' => 'end_event', + 'process_id' => $process->id, + 'process_request_id' => $request->id, + 'status' => 'CLOSED', + ]); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + + $this->apiCall('GET', route('api.requests.end_event_destination', [$request])) + ->assertStatus(200) + ->assertJsonPath('data.endEventDestination.type', 'summaryScreen') + ->assertJsonPath('data.endEventDestination.value', null); + } + + public function testProcessManagerCannotViewRequestFromUnmanagedProcess() + { + $managedProcess = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $unmanagedRequest = ProcessRequest::factory()->create(); + + $this->assertNotEquals($managedProcess->id, $unmanagedRequest->process_id); + + $this->apiCall('GET', route('api.requests.show', [$unmanagedRequest])) + ->assertStatus(403); + + $this->apiCall('GET', route('api.requests.end_event_destination', [$unmanagedRequest])) + ->assertStatus(403); + } + + public function testProcessManagerAccessIsRevokedWhenRemovedFromProcess() + { + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + + $process->manager_id = []; + $process->save(); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(403); + } + + public function testProcessManagerCannotUpdateOrDeleteManagedRequest() + { + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $this->apiCall('PUT', route('api.requests.update', [$request]), [ + 'name' => 'Unauthorized update', + ])->assertStatus(403); + + $this->apiCall('DELETE', route('api.requests.destroy', [$request])) + ->assertStatus(403); + } + + public function testManagedRequestIsNotAddedToRequestListing() + { + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + + $this->apiCall('GET', route('api.requests.index')) + ->assertStatus(200) + ->assertJsonMissing(['id' => $request->id]); + } + + public function testScalarProcessManagerValueIsNormalizedForAuthorization() + { + $process = Process::factory()->create([ + 'manager_id' => $this->user->id, + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $this->assertSame([$this->user->id], $process->fresh()->manager_id); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + } + + public function testProcessManagerReassignmentUpdatesRequestAccess() + { + $newManager = User::factory()->create(); + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + + $process->manager_id = [$newManager->id]; + $process->save(); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(403); + + $this->user = $newManager; + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + } + + public function testProcessManagerCanViewManagedRequestsInEveryStatus() + { + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + + foreach (['ACTIVE', 'COMPLETED', 'CANCELED', 'ERROR'] as $status) { + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + 'status' => $status, + ]); + + $this->apiCall('GET', route('api.requests.show', [$request])) + ->assertStatus(200); + } + } + + public function testEndEventDestinationReturnsNullWhenManagedRequestHasNoEndEvent() + { + $process = Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + 'status' => 'COMPLETED', + ]); + + $this->apiCall('GET', route('api.requests.end_event_destination', [$request])) + ->assertStatus(200) + ->assertJsonPath('data', null); + } + + public function testEndEventDestinationReturnsConfiguredExternalUrlForProcessManager() + { + $bpmn = file_get_contents(__DIR__ . '/../../Fixtures/action_by_email_process_no_require_login.bpmn'); + $bpmn = str_replace( + '{"type":"summaryScreen","value":null}', + '{"type":"externalURL","value":"https://example.com/summary"}', + $bpmn, + ); + $process = Process::factory()->create([ + 'bpmn' => $bpmn, + 'manager_id' => [$this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + 'status' => 'COMPLETED', + ]); + ProcessRequestToken::factory()->create([ + 'element_id' => 'node_18', + 'element_type' => 'end_event', + 'process_id' => $process->id, + 'process_request_id' => $request->id, + 'status' => 'CLOSED', + ]); + + $this->apiCall('GET', route('api.requests.end_event_destination', [$request])) + ->assertStatus(200) + ->assertJsonPath('data.endEventDestination.type', 'externalURL') + ->assertJsonPath('data.endEventDestination.value', 'https://example.com/summary'); + } } diff --git a/tests/Feature/CasesControllerTest.php b/tests/Feature/CasesControllerTest.php index 23f182665f..2ac59a0208 100644 --- a/tests/Feature/CasesControllerTest.php +++ b/tests/Feature/CasesControllerTest.php @@ -62,6 +62,31 @@ public function testShowCaseWithUserWithoutParticipation() $response->assertStatus(403); } + public function testProcessManagerCanOpenManagedCaseButUnrelatedUserCannot() + { + $processManager = User::factory()->create(); + $firstManager = User::factory()->create(); + $process = Process::factory()->create([ + 'manager_id' => [$firstManager->id, $processManager->id], + ]); + $request = ProcessRequest::factory()->create([ + 'parent_request_id' => null, + 'process_id' => $process->id, + 'status' => 'COMPLETED', + ]); + $route = route('cases.show', ['case_number' => $request->case_number]); + + $response = $this->actingAs($processManager)->get($route); + + $response->assertStatus(200); + $response->assertViewIs('cases.edit'); + $response->assertViewHas('isProcessManager', true); + + $this->actingAs(User::factory()->create()) + ->get($route) + ->assertStatus(403); + } + public function testCasesAllPageReturns403WithoutViewAllCasesPermission() { Permission::firstOrCreate( diff --git a/tests/Feature/RequestTest.php b/tests/Feature/RequestTest.php index ac86ed05f2..690820d611 100644 --- a/tests/Feature/RequestTest.php +++ b/tests/Feature/RequestTest.php @@ -3,6 +3,8 @@ namespace Tests\Feature; use Illuminate\Http\Testing\File; +use Illuminate\Support\Facades\Gate; +use ProcessMaker\Models\Permission; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessCategory; use ProcessMaker\Models\ProcessRequest; @@ -123,6 +125,54 @@ public function testShowRouteWithAssignedUser() $response->assertStatus(200); } + public function testProcessManagerCanViewManagedRequestSummary() + { + $this->user = User::factory()->create(); + $firstManager = User::factory()->create(); + $process = Process::factory()->create([ + 'manager_id' => [$firstManager->id, $this->user->id], + ]); + $request = ProcessRequest::factory()->create([ + 'process_id' => $process->id, + 'status' => 'COMPLETED', + ]); + + $response = $this->webCall('GET', route('requests.show', [$request])); + + $response->assertStatus(200); + $response->assertViewIs('requests.show'); + $response->assertViewHas('isProcessManager', true); + } + + public function testProcessManagerCannotViewUnmanagedRequestSummary() + { + $this->user = User::factory()->create(); + Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + $unmanagedRequest = ProcessRequest::factory()->create(); + + $this->webCall('GET', route('requests.show', [$unmanagedRequest])) + ->assertStatus(403); + } + + public function testProcessManagerCannotViewAllRequestsWithoutPermission() + { + Permission::firstOrCreate( + ['name' => 'view-all_requests'], + ['title' => 'View All Requests'], + ); + Gate::define('view-all_requests', fn ($user) => $user->hasPermission('view-all_requests')); + + $this->user = User::factory()->create(); + Process::factory()->create([ + 'manager_id' => [$this->user->id], + ]); + + $this->webCall('GET', route('requests_by_type', ['type' => 'all'])) + ->assertStatus(403); + } + public function testShowRouteWithAdministrator() { $this->user = User::factory()->create([