fix(security): enforce ownership on GET weekly-schedule (IDOR)
getSchedule returned any doctor's schedule to any authenticated user — the mutation endpoints (update/delete) already checked owner-or-admin but this GET did not. Add the same check + regression test (fails without the fix).
This commit is contained in:
@@ -109,7 +109,7 @@ class AppointmentSettingsController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/v1/appointment-settings/weekly-schedule/{uuid}', methods: ['GET'])]
|
#[Route('/api/v1/appointment-settings/weekly-schedule/{uuid}', methods: ['GET'])]
|
||||||
public function getSchedule(string $uuid): JsonResponse
|
public function getSchedule(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||||
{
|
{
|
||||||
// Try doctor uuid first, then schedule uuid
|
// Try doctor uuid first, then schedule uuid
|
||||||
$doctor = $this->doctorRepo->findByUuid($uuid);
|
$doctor = $this->doctorRepo->findByUuid($uuid);
|
||||||
@@ -121,6 +121,10 @@ class AppointmentSettingsController extends BaseController
|
|||||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
|
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($schedule->getDoctor()->getUser()->getId() !== $user->getId() && !$user->hasRole('ROLE_ADMIN')) {
|
||||||
|
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->success(['data' => $schedule->toArray()]);
|
return $this->success(['data' => $schedule->toArray()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Appointment;
|
||||||
|
|
||||||
|
use App\Appointment\Entity\WeeklySchedule;
|
||||||
|
use App\Auth\Entity\User;
|
||||||
|
use App\Doctor\Entity\Doctor;
|
||||||
|
use App\Tests\ApiTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression: GET weekly-schedule must enforce ownership (IDOR fix).
|
||||||
|
* Before the fix any authenticated user could read any doctor's schedule.
|
||||||
|
*/
|
||||||
|
class ScheduleOwnershipTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private function makeDoctorWithSchedule(): array
|
||||||
|
{
|
||||||
|
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||||
|
$doctor = new Doctor($owner, 'دکتر تست');
|
||||||
|
$this->em->persist($doctor);
|
||||||
|
$schedule = new WeeklySchedule($doctor, ['sat' => []]);
|
||||||
|
$this->em->persist($schedule);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
return [$owner, $doctor];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOwnerCanReadOwnSchedule(): void
|
||||||
|
{
|
||||||
|
[$owner, $doctor] = $this->makeDoctorWithSchedule();
|
||||||
|
$this->authJson('GET', '/api/v1/appointment-settings/weekly-schedule/' . $doctor->getUuid(), $owner);
|
||||||
|
$this->assertSame(200, $this->responseCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOtherUserIsForbidden(): void
|
||||||
|
{
|
||||||
|
[, $doctor] = $this->makeDoctorWithSchedule();
|
||||||
|
$attacker = $this->createUser(['ROLE_DOCTOR']);
|
||||||
|
$this->authJson('GET', '/api/v1/appointment-settings/weekly-schedule/' . $doctor->getUuid(), $attacker);
|
||||||
|
$this->assertSame(403, $this->responseCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdminCanRead(): void
|
||||||
|
{
|
||||||
|
[, $doctor] = $this->makeDoctorWithSchedule();
|
||||||
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
||||||
|
$this->authJson('GET', '/api/v1/appointment-settings/weekly-schedule/' . $doctor->getUuid(), $admin);
|
||||||
|
$this->assertSame(200, $this->responseCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user