feat: Implement secretary permissions enforcement across multiple resources
- Added SecretaryAccessChecker to manage resource access for secretaries. - Integrated permission checks for payments, inventory, and tags in relevant controllers. - Updated PaymentController and PaymentMethodController to enforce secretary permissions. - Enhanced TenantTagController to check permissions for tag management actions. - Introduced tests for secretary resource enforcement, ensuring proper access control. - Updated DoctorSecretary entity to include inventory and tags permissions. - Created a comprehensive audit document for secretary permissions coverage and enforcement. - Fixed potential crashes in SecretaryDashboard when rendering without doctor data.
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Inventory\Entity\InventoryPackage;
|
||||
use App\Inventory\Repository\InventoryItemRepository;
|
||||
use App\Inventory\Repository\InventoryPackageRepository;
|
||||
use App\Inventory\Service\InventoryService;
|
||||
use App\Secretary\Security\SecretaryAccessChecker;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use App\Shared\Exception\AppException;
|
||||
@@ -37,6 +38,7 @@ class InventoryController extends BaseController
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly UserActiveContextRepository $contextRepo,
|
||||
private readonly SecretaryAccessChecker $secretaryAccess,
|
||||
) {}
|
||||
|
||||
// ── Items ────────────────────────────────────────────────────────────────
|
||||
@@ -44,6 +46,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-items', methods: ['GET'])]
|
||||
public function listItems(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
|
||||
[$type, $id] = $this->resolveEntity($user);
|
||||
if ($id === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
@@ -60,6 +63,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-categories', methods: ['GET'])]
|
||||
public function listCategories(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
|
||||
[$type, $id] = $this->resolveEntity($user);
|
||||
if ($id === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
@@ -80,6 +84,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-item', methods: ['POST'])]
|
||||
public function createItem(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
|
||||
[$type, $id] = $this->resolveEntity($user);
|
||||
if ($id === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
@@ -101,6 +106,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-item/{uuid}', methods: ['PATCH'])]
|
||||
public function updateItem(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
|
||||
$item = $this->ownedItem($uuid, $user);
|
||||
if ($item === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
|
||||
@@ -123,6 +129,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-item/{uuid}', methods: ['DELETE'])]
|
||||
public function deleteItem(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
|
||||
$item = $this->ownedItem($uuid, $user);
|
||||
if ($item === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
|
||||
@@ -138,6 +145,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-packages', methods: ['GET'])]
|
||||
public function listPackages(#[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
|
||||
[$type, $id] = $this->resolveEntity($user);
|
||||
if ($id === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
@@ -152,6 +160,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-package', methods: ['POST'])]
|
||||
public function createPackage(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
|
||||
[$type, $id] = $this->resolveEntity($user);
|
||||
if ($id === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
@@ -173,6 +182,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-package/{uuid}', methods: ['PATCH'])]
|
||||
public function updatePackage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
|
||||
$package = $this->ownedPackage($uuid, $user);
|
||||
if ($package === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
|
||||
@@ -199,6 +209,7 @@ class InventoryController extends BaseController
|
||||
#[Route('/api/v1/inventory-package/{uuid}', methods: ['DELETE'])]
|
||||
public function deletePackage(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
|
||||
$package = $this->ownedPackage($uuid, $user);
|
||||
if ($package === null) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
|
||||
|
||||
Reference in New Issue
Block a user