diff --git a/config/sidebar.php b/config/sidebar.php index 1d9ad69..b4761d4 100644 --- a/config/sidebar.php +++ b/config/sidebar.php @@ -23,9 +23,9 @@ return [ 'icon' => 'mdi:view-dashboard-outline', 'items' => [ ['label' => 'sidebar.general.dashboard', 'to' => '/', 'icon' => 'mdi:view-dashboard-outline'], - ['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline', 'module' => 'project-management'], - ['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline', 'module' => 'project-management'], - ['label' => 'sidebar.general.timeTracking', 'to' => '/time-tracking', 'icon' => 'mdi:calendar-edit-outline', 'module' => 'time-tracking'], + ['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline', 'module' => 'project-management', 'permission' => 'project-management.tasks.view'], + ['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline', 'module' => 'project-management', 'permission' => 'project-management.projects.view'], + ['label' => 'sidebar.general.timeTracking', 'to' => '/time-tracking', 'icon' => 'mdi:calendar-edit-outline', 'module' => 'time-tracking', 'permission' => 'time-tracking.entries.view'], // Gating module uniquement (cf. en-tête) : rendu visuel + badge gérés côté layout. ['label' => 'sidebar.general.mail', 'to' => '/mail', 'icon' => 'mdi:email-outline', 'module' => 'mail'], ], diff --git a/src/Module/Core/Infrastructure/Security/PermissionVoter.php b/src/Module/Core/Infrastructure/Security/PermissionVoter.php index 542e220..36262fc 100644 --- a/src/Module/Core/Infrastructure/Security/PermissionVoter.php +++ b/src/Module/Core/Infrastructure/Security/PermissionVoter.php @@ -14,7 +14,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; */ final class PermissionVoter extends Voter { - private const string PATTERN = '/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/'; + // Les codes de permission sont au format module.resource.action où chaque + // segment peut contenir des tirets (ex. project-management, time-tracking). + private const string PATTERN = '/^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)+$/'; protected function supports(string $attribute, mixed $subject): bool { diff --git a/src/Module/Directory/Domain/Entity/Address.php b/src/Module/Directory/Domain/Entity/Address.php index 00c2e45..0e8740f 100644 --- a/src/Module/Directory/Domain/Entity/Address.php +++ b/src/Module/Directory/Domain/Entity/Address.php @@ -23,11 +23,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[Auditable] #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Get(security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Post(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Patch(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Delete(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), ], normalizationContext: ['groups' => ['address:read']], denormalizationContext: ['groups' => ['address:write']], diff --git a/src/Module/Directory/Domain/Entity/Client.php b/src/Module/Directory/Domain/Entity/Client.php index 9d420be..6ca461f 100644 --- a/src/Module/Directory/Domain/Entity/Client.php +++ b/src/Module/Directory/Domain/Entity/Client.php @@ -25,11 +25,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[Auditable] #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.clients.view')"), + new Get(security: "is_granted('directory.clients.view')"), + new Post(security: "is_granted('directory.clients.manage')"), + new Patch(security: "is_granted('directory.clients.manage')"), + new Delete(security: "is_granted('directory.clients.manage')"), ], normalizationContext: ['groups' => ['client:read']], denormalizationContext: ['groups' => ['client:write']], diff --git a/src/Module/Directory/Domain/Entity/CommercialReport.php b/src/Module/Directory/Domain/Entity/CommercialReport.php index ce55791..6f5a7b8 100644 --- a/src/Module/Directory/Domain/Entity/CommercialReport.php +++ b/src/Module/Directory/Domain/Entity/CommercialReport.php @@ -26,11 +26,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Get(security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Post(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Patch(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Delete(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), ], normalizationContext: ['groups' => ['commercial_report:read']], denormalizationContext: ['groups' => ['commercial_report:write']], diff --git a/src/Module/Directory/Domain/Entity/Contact.php b/src/Module/Directory/Domain/Entity/Contact.php index 0f9e1d5..e44ae47 100644 --- a/src/Module/Directory/Domain/Entity/Contact.php +++ b/src/Module/Directory/Domain/Entity/Contact.php @@ -23,11 +23,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[Auditable] #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Get(security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Post(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Patch(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), + new Delete(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), ], normalizationContext: ['groups' => ['contact:read']], denormalizationContext: ['groups' => ['contact:write']], diff --git a/src/Module/Directory/Domain/Entity/Prospect.php b/src/Module/Directory/Domain/Entity/Prospect.php index cd0f213..775d2d0 100644 --- a/src/Module/Directory/Domain/Entity/Prospect.php +++ b/src/Module/Directory/Domain/Entity/Prospect.php @@ -27,14 +27,14 @@ use Symfony\Component\Serializer\Attribute\Groups; #[Auditable] #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.prospects.view')"), + new Get(security: "is_granted('directory.prospects.view')"), + new Post(security: "is_granted('directory.prospects.manage')"), + new Patch(security: "is_granted('directory.prospects.manage')"), + new Delete(security: "is_granted('directory.prospects.manage')"), new Post( uriTemplate: '/prospects/{id}/convert', - security: "is_granted('ROLE_ADMIN')", + security: "is_granted('directory.prospects.manage')", processor: ConvertProspectProcessor::class, ), ], diff --git a/src/Module/Directory/Domain/Entity/ReportDocument.php b/src/Module/Directory/Domain/Entity/ReportDocument.php index 4c4a8b5..b23273b 100644 --- a/src/Module/Directory/Domain/Entity/ReportDocument.php +++ b/src/Module/Directory/Domain/Entity/ReportDocument.php @@ -20,14 +20,14 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), + new GetCollection(paginationEnabled: false, security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), + new Get(security: "is_granted('directory.clients.view') or is_granted('directory.prospects.view')"), new Post( - security: "is_granted('ROLE_ADMIN')", + security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')", processor: ReportDocumentProcessor::class, deserialize: false, ), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new Delete(security: "is_granted('directory.clients.manage') or is_granted('directory.prospects.manage')"), ], normalizationContext: ['groups' => ['report_document:read']], denormalizationContext: ['groups' => ['report_document:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/Project.php b/src/Module/ProjectManagement/Domain/Entity/Project.php index 793883e..b788704 100644 --- a/src/Module/ProjectManagement/Domain/Entity/Project.php +++ b/src/Module/ProjectManagement/Domain/Entity/Project.php @@ -30,18 +30,18 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view')"), + new Get(security: "is_granted('project-management.projects.view')"), new Post( - security: "is_granted('ROLE_ADMIN')", + security: "is_granted('project-management.projects.manage')", denormalizationContext: ['groups' => ['project:write', 'project:create']], ), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new Patch(security: "is_granted('project-management.projects.manage')"), + new Delete(security: "is_granted('project-management.projects.manage')"), new Post( uriTemplate: '/projects/{id}/switch-workflow', uriVariables: ['id' => new Link(fromClass: Project::class)], - security: "is_granted('ROLE_ADMIN')", + security: "is_granted('project-management.projects.manage')", input: false, output: SwitchWorkflowOutput::class, normalizationContext: ['groups' => ['switch_workflow:read']], diff --git a/src/Module/ProjectManagement/Domain/Entity/Task.php b/src/Module/ProjectManagement/Domain/Entity/Task.php index 661a531..7725ac5 100644 --- a/src/Module/ProjectManagement/Domain/Entity/Task.php +++ b/src/Module/ProjectManagement/Domain/Entity/Task.php @@ -33,11 +33,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')", processor: TaskNumberProcessor::class), - new Patch(security: "is_granted('ROLE_ADMIN')", processor: TaskCalendarProcessor::class), - new Delete(security: "is_granted('ROLE_ADMIN')", processor: TaskCalendarProcessor::class), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.tasks.manage')", processor: TaskNumberProcessor::class), + new Patch(security: "is_granted('project-management.tasks.manage')", processor: TaskCalendarProcessor::class), + new Delete(security: "is_granted('project-management.tasks.manage')", processor: TaskCalendarProcessor::class), ], normalizationContext: ['groups' => ['task:read']], denormalizationContext: ['groups' => ['task:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskDocument.php b/src/Module/ProjectManagement/Domain/Entity/TaskDocument.php index 3d2d5f3..1e1645a 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskDocument.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskDocument.php @@ -21,14 +21,14 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')", provider: TaskDocumentProvider::class), - new Get(security: "is_granted('ROLE_USER')", provider: TaskDocumentProvider::class), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.tasks.view')", provider: TaskDocumentProvider::class), + new Get(security: "is_granted('project-management.tasks.view')", provider: TaskDocumentProvider::class), new Post( - security: "is_granted('ROLE_ADMIN')", + security: "is_granted('project-management.tasks.manage')", processor: TaskDocumentProcessor::class, deserialize: false, ), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new Delete(security: "is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_document:read']], denormalizationContext: ['groups' => ['task_document:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskEffort.php b/src/Module/ProjectManagement/Domain/Entity/TaskEffort.php index 5dabd51..4d67786 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskEffort.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskEffort.php @@ -16,11 +16,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_effort:read']], denormalizationContext: ['groups' => ['task_effort:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskGroup.php b/src/Module/ProjectManagement/Domain/Entity/TaskGroup.php index c013da7..5c5193d 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskGroup.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskGroup.php @@ -19,11 +19,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_group:read']], denormalizationContext: ['groups' => ['task_group:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskPriority.php b/src/Module/ProjectManagement/Domain/Entity/TaskPriority.php index 9840bee..2c2572d 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskPriority.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskPriority.php @@ -16,11 +16,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_priority:read']], denormalizationContext: ['groups' => ['task_priority:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskRecurrence.php b/src/Module/ProjectManagement/Domain/Entity/TaskRecurrence.php index eb82b3e..e2b3362 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskRecurrence.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskRecurrence.php @@ -20,11 +20,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_recurrence:read']], denormalizationContext: ['groups' => ['task_recurrence:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskStatus.php b/src/Module/ProjectManagement/Domain/Entity/TaskStatus.php index 301d417..4797808 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskStatus.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskStatus.php @@ -18,11 +18,11 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_status:read']], denormalizationContext: ['groups' => ['task_status:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/TaskTag.php b/src/Module/ProjectManagement/Domain/Entity/TaskTag.php index 136cc91..7f8ec7f 100644 --- a/src/Module/ProjectManagement/Domain/Entity/TaskTag.php +++ b/src/Module/ProjectManagement/Domain/Entity/TaskTag.php @@ -17,11 +17,11 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')"), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), ], normalizationContext: ['groups' => ['task_tag:read']], denormalizationContext: ['groups' => ['task_tag:write']], diff --git a/src/Module/ProjectManagement/Domain/Entity/Workflow.php b/src/Module/ProjectManagement/Domain/Entity/Workflow.php index 1afa1a3..f69b962 100644 --- a/src/Module/ProjectManagement/Domain/Entity/Workflow.php +++ b/src/Module/ProjectManagement/Domain/Entity/Workflow.php @@ -21,11 +21,11 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ - new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), - new Patch(security: "is_granted('ROLE_ADMIN')"), - new Delete(security: "is_granted('ROLE_ADMIN')", processor: WorkflowDeleteProcessor::class), + new GetCollection(paginationEnabled: false, security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Get(security: "is_granted('project-management.projects.view') or is_granted('project-management.tasks.view')"), + new Post(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Patch(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')"), + new Delete(security: "is_granted('project-management.projects.manage') or is_granted('project-management.tasks.manage')", processor: WorkflowDeleteProcessor::class), ], normalizationContext: ['groups' => ['workflow:read']], denormalizationContext: ['groups' => ['workflow:write']], diff --git a/src/Module/TimeTracking/Domain/Entity/TimeEntry.php b/src/Module/TimeTracking/Domain/Entity/TimeEntry.php index 1ed090d..5da02ee 100644 --- a/src/Module/TimeTracking/Domain/Entity/TimeEntry.php +++ b/src/Module/TimeTracking/Domain/Entity/TimeEntry.php @@ -31,13 +31,13 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ - new GetCollection(security: "is_granted('ROLE_USER')"), + new GetCollection(security: "is_granted('time-tracking.entries.view')"), new GetCollection( name: 'time_entries_range', uriTemplate: '/time_entries/range', description: 'List time entries for a bounded date range without pagination (used by the time-tracking calendar)', paginationEnabled: false, - security: "is_granted('ROLE_USER')", + security: "is_granted('time-tracking.entries.view')", ), new GetCollection( name: 'active_time_entry', @@ -45,12 +45,12 @@ use Symfony\Component\Serializer\Attribute\Groups; provider: ActiveTimeEntryProvider::class, description: 'Get the active timer for the current user', paginationEnabled: false, - security: "is_granted('ROLE_USER')", + security: "is_granted('time-tracking.entries.view')", ), - new Get(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_USER')"), - new Patch(security: "is_granted('ROLE_ADMIN') or object.getUser() == user"), - new Delete(security: "is_granted('ROLE_ADMIN') or object.getUser() == user"), + new Get(security: "is_granted('time-tracking.entries.view')"), + new Post(security: "is_granted('time-tracking.entries.view')"), + new Patch(security: "is_granted('ROLE_ADMIN') or (is_granted('time-tracking.entries.view') and object.getUser() == user)"), + new Delete(security: "is_granted('ROLE_ADMIN') or (is_granted('time-tracking.entries.view') and object.getUser() == user)"), ], normalizationContext: ['groups' => ['time_entry:read']], denormalizationContext: ['groups' => ['time_entry:write']], diff --git a/tests/Functional/Module/ProjectManagement/ProjectAccessControlTest.php b/tests/Functional/Module/ProjectManagement/ProjectAccessControlTest.php new file mode 100644 index 0000000..455aad5 --- /dev/null +++ b/tests/Functional/Module/ProjectManagement/ProjectAccessControlTest.php @@ -0,0 +1,82 @@ +get(EntityManagerInterface::class); + + $user = $this->createPlainUser($em, 'proj-noperm-'.uniqid()); + $em->flush(); + $client->loginUser($user); + + $client->request('GET', '/api/projects'); + + self::assertResponseStatusCodeSame(403); + } + + public function testUserWithViewPermissionCanListProjects(): void + { + $client = self::createClient(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $permission = $em->getRepository(Permission::class)->findOneBy(['code' => 'project-management.projects.view']); + self::assertInstanceOf(Permission::class, $permission, 'Le catalogue de permissions doit contenir project-management.projects.view (lancer app:sync-permissions).'); + + $user = $this->createPlainUser($em, 'proj-view-'.uniqid()); + $user->addDirectPermission($permission); + $em->flush(); + $client->loginUser($user); + + $client->request('GET', '/api/projects'); + + self::assertResponseIsSuccessful(); + } + + public function testViewPermissionDoesNotGrantWrite(): void + { + $client = self::createClient(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $permission = $em->getRepository(Permission::class)->findOneBy(['code' => 'project-management.projects.view']); + self::assertInstanceOf(Permission::class, $permission); + + $user = $this->createPlainUser($em, 'proj-noWrite-'.uniqid()); + $user->addDirectPermission($permission); + $em->flush(); + $client->loginUser($user); + + $client->request('POST', '/api/projects', server: [ + 'CONTENT_TYPE' => 'application/ld+json', + ], content: json_encode(['name' => 'Should be denied'])); + + self::assertResponseStatusCodeSame(403); + } + + private function createPlainUser(EntityManagerInterface $em, string $username): User + { + $user = new User(); + $user->setUsername($username); + $user->setPassword('x'); + $user->setRoles(['ROLE_USER']); + $em->persist($user); + + return $user; + } +}