Преглед изворни кода

Merge branch 'v8-4328-security_revision' into release/2.4.beta

Olivier Massot пре 2 година
родитељ
комит
e0dbed6c5c
100 измењених фајлова са 1022 додато и 748 уклоњено
  1. 3 1
      .gitlab-ci.yml
  2. 27 27
      composer.json
  3. 0 26
      config/api_platform/Access/access.yaml
  4. 2 2
      config/services.yaml
  5. 3 0
      doc/internal_requests.md
  6. 186 0
      doc/security.md
  7. 1 14
      src/ApiResources/Access/AdminAccess.php
  8. 10 9
      src/ApiResources/Cotisation/Cotisation.php
  9. 37 7
      src/Doctrine/AbstractExtension.php
  10. 0 11
      src/Doctrine/Access/AccessExtensionInterface.php
  11. 15 0
      src/Doctrine/Access/AdditionalExtension/AdditionalAccessExtensionInterface.php
  12. 6 3
      src/Doctrine/Access/AdditionalExtension/DateTimeConstraintExtensionAdditional.php
  13. 7 4
      src/Doctrine/Access/AdditionalExtension/StudentsExtensionAdditional.php
  14. 6 32
      src/Doctrine/Access/CurrentAccessExtension.php
  15. 7 7
      src/Doctrine/Access/CurrentUserPersonalizedListExtension.php
  16. 7 6
      src/Doctrine/Billing/CurrentResidenceAreaExtension.php
  17. 7 6
      src/Doctrine/Booking/CurrentCoursesExtension.php
  18. 6 5
      src/Doctrine/Core/AllowedAddressPostalExtension.php
  19. 7 6
      src/Doctrine/Core/CurrentUserNotificationExtension.php
  20. 7 6
      src/Doctrine/Core/CurrentUserNotificationUserExtension.php
  21. 6 7
      src/Doctrine/Education/CurrentCycleExtension.php
  22. 7 6
      src/Doctrine/Education/CurrentEducationNotationConfigExtension.php
  23. 7 6
      src/Doctrine/Education/CurrentEducationTimingExtension.php
  24. 7 6
      src/Doctrine/Network/CurrentNetworkOrganizationExtension.php
  25. 7 6
      src/Doctrine/Organization/CurrentOrganizationAddressPostalExtension.php
  26. 7 6
      src/Doctrine/Organization/CurrentOrganizationArticleExtension.php
  27. 6 5
      src/Doctrine/Organization/CurrentOrganizationExtension.php
  28. 6 1
      src/Entity/Access/Access.php
  29. 2 8
      src/Entity/Access/AccessFamily.php
  30. 1 1
      src/Entity/Access/OrganizationFunction.php
  31. 6 1
      src/Entity/Access/PersonalizedList.php
  32. 2 8
      src/Entity/Billing/AccessBilling.php
  33. 1 1
      src/Entity/Billing/AccessIntangible.php
  34. 1 7
      src/Entity/Billing/AccessPayer.php
  35. 2 8
      src/Entity/Billing/Bill.php
  36. 1 7
      src/Entity/Billing/BillAccounting.php
  37. 2 8
      src/Entity/Billing/BillCredit.php
  38. 2 8
      src/Entity/Billing/BillLine.php
  39. 1 7
      src/Entity/Billing/BillingIntangibleExcludeDate.php
  40. 1 5
      src/Entity/Billing/BillingSetting.php
  41. 2 8
      src/Entity/Billing/EducationalProjectPayer.php
  42. 2 8
      src/Entity/Billing/FamilyQuotient.php
  43. 5 18
      src/Entity/Billing/ResidenceArea.php
  44. 2 8
      src/Entity/Booking/Attendance.php
  45. 1 7
      src/Entity/Booking/AttendanceBooking.php
  46. 5 9
      src/Entity/Booking/Course.php
  47. 2 8
      src/Entity/Booking/EducationalProject.php
  48. 1 7
      src/Entity/Booking/Event.php
  49. 2 8
      src/Entity/Booking/EventUser.php
  50. 2 8
      src/Entity/Booking/Examen.php
  51. 2 8
      src/Entity/Booking/OrganizationHoliday.php
  52. 2 8
      src/Entity/Booking/PersonHoliday.php
  53. 1 1
      src/Entity/Core/AbstractInformation.php
  54. 7 8
      src/Entity/Core/AddressPostal.php
  55. 4 15
      src/Entity/Core/BankAccount.php
  56. 4 15
      src/Entity/Core/ContactPoint.php
  57. 46 6
      src/Entity/Core/File.php
  58. 4 1
      src/Entity/Core/Notification.php
  59. 3 0
      src/Entity/Core/NotificationUser.php
  60. 2 8
      src/Entity/Core/Tagg.php
  61. 1 10
      src/Entity/Core/Tips.php
  62. 1 7
      src/Entity/Donor/Donor.php
  63. 2 8
      src/Entity/Education/CriteriaNotation.php
  64. 4 14
      src/Entity/Education/Cycle.php
  65. 2 8
      src/Entity/Education/Education.php
  66. 2 8
      src/Entity/Education/EducationCategory.php
  67. 1 7
      src/Entity/Education/EducationCurriculum.php
  68. 2 8
      src/Entity/Education/EducationNotation.php
  69. 5 14
      src/Entity/Education/EducationNotationConfig.php
  70. 2 8
      src/Entity/Education/EducationStudent.php
  71. 2 8
      src/Entity/Education/EducationTeacher.php
  72. 5 18
      src/Entity/Education/EducationTiming.php
  73. 1 7
      src/Entity/Network/Network.php
  74. 4 13
      src/Entity/Network/NetworkOrganization.php
  75. 2 8
      src/Entity/Organization/Activity.php
  76. 2 8
      src/Entity/Organization/Jury.php
  77. 6 13
      src/Entity/Organization/Organization.php
  78. 7 16
      src/Entity/Organization/OrganizationAddressPostal.php
  79. 5 12
      src/Entity/Organization/OrganizationArticle.php
  80. 1 1
      src/Entity/Organization/OrganizationLicence.php
  81. 1 1
      src/Entity/Organization/Settings.php
  82. 8 13
      src/Entity/Organization/Subdomain.php
  83. 2 10
      src/Entity/Organization/TypeOfPractice.php
  84. 2 8
      src/Entity/Person/Commission.php
  85. 1 7
      src/Entity/Person/CommissionMember.php
  86. 2 8
      src/Entity/Person/CompanyPerson.php
  87. 1 7
      src/Entity/Person/Person.php
  88. 1 1
      src/Entity/Person/PersonActivity.php
  89. 1 7
      src/Entity/Person/PersonAddressPostal.php
  90. 1 7
      src/Entity/Place/Place.php
  91. 2 8
      src/Entity/Place/Room.php
  92. 2 8
      src/Entity/Product/Equipment.php
  93. 1 7
      src/Entity/Product/EquipmentLoan.php
  94. 2 8
      src/Entity/Product/Intangible.php
  95. 2 8
      src/Entity/Product/IntangibleDiscountDetail.php
  96. 2 0
      src/Enum/Core/FileTypeEnum.php
  97. 19 0
      src/Enum/Core/FileVisibilityEnum.php
  98. 187 0
      src/Security/Voter/AbstractVoter.php
  99. 208 0
      src/Security/Voter/Core/FileVoter.php
  100. 2 1
      src/Security/Voter/SwitchUserVoter.php

+ 3 - 1
.gitlab-ci.yml

@@ -16,7 +16,9 @@ before_script:
 
 static_analysis:
   script:
-    - php --version && php -d memory_limit=512M vendor/bin/phpstan analyse -c phpstan.neon.dist --error-format gitlab > phpstan.json
+    - php --version
+    - php -d memory_limit=512M vendor/bin/phpstan analyse -c phpstan.neon.dist --error-format gitlab > phpstan.json  # Display code quality in MR
+    - php -d memory_limit=512M vendor/bin/phpstan analyse -c phpstan.neon.dist  # Display in console
   artifacts:
     when: always
     reports:

+ 27 - 27
composer.json

@@ -32,33 +32,33 @@
         "phpdocumentor/reflection-docblock": "^5.2",
         "ramsey/uuid": "^4.2",
         "ramsey/uuid-doctrine": "^2.0",
-        "symfony/asset": "6.2.*",
-        "symfony/console": "6.2.*",
-        "symfony/doctrine-messenger": "6.2.*",
-        "symfony/dotenv": "6.2.*",
-        "symfony/error-handler": "6.2.*",
-        "symfony/expression-language": "6.2.*",
+        "symfony/asset": "6.3.*",
+        "symfony/console": "6.3.*",
+        "symfony/doctrine-messenger": "6.3.*",
+        "symfony/dotenv": "6.3.*",
+        "symfony/error-handler": "6.3.*",
+        "symfony/expression-language": "6.3.*",
         "symfony/flex": "^1.3.1",
-        "symfony/framework-bundle": "6.2.*",
-        "symfony/http-client": "6.2.*",
-        "symfony/intl": "6.2.*",
-        "symfony/lock": "6.2.*",
-        "symfony/mailer": "6.2.*",
+        "symfony/framework-bundle": "6.3.*",
+        "symfony/http-client": "6.3.*",
+        "symfony/intl": "6.3.*",
+        "symfony/lock": "6.3.*",
+        "symfony/mailer": "6.3.*",
         "symfony/mercure": "^0.6.1",
         "symfony/mercure-bundle": "^0.3.4",
-        "symfony/messenger": "6.2.*",
+        "symfony/messenger": "6.3.*",
         "symfony/monolog-bundle": "^3.7",
         "symfony/polyfill-intl-icu": "^1.21",
         "symfony/polyfill-intl-messageformatter": "^1.24",
-        "symfony/property-access": "6.2.*",
-        "symfony/property-info": "6.2.*",
-        "symfony/security-bundle": "6.2.*",
-        "symfony/serializer": "6.2.*",
-        "symfony/translation": "6.2.*",
-        "symfony/twig-bundle": "6.2.*",
-        "symfony/uid": "6.2.*",
-        "symfony/validator": "6.2.*",
-        "symfony/yaml": "6.2.*",
+        "symfony/property-access": "6.3.*",
+        "symfony/property-info": "6.3.*",
+        "symfony/security-bundle": "6.3.*",
+        "symfony/serializer": "6.3.*",
+        "symfony/translation": "6.3.*",
+        "symfony/twig-bundle": "6.3.*",
+        "symfony/uid": "6.3.*",
+        "symfony/validator": "6.3.*",
+        "symfony/yaml": "6.3.*",
         "twig/cssinliner-extra": "^3.4",
         "twig/extra-bundle": "^3.4",
         "twig/inky-extra": "^3.4",
@@ -79,13 +79,13 @@
         "phpstan/phpstan-symfony": "^1.2",
         "phpunit/phpunit": "^9.6",
         "rector/rector": "^0.15.13",
-        "symfony/browser-kit": "6.2.*",
-        "symfony/css-selector": "6.2.*",
-        "symfony/debug-bundle": "6.2.*",
+        "symfony/browser-kit": "6.3.*",
+        "symfony/css-selector": "6.3.*",
+        "symfony/debug-bundle": "6.3.*",
         "symfony/maker-bundle": "^1.21",
         "symfony/phpunit-bridge": "^6.2",
-        "symfony/stopwatch": "6.2.*",
-        "symfony/web-profiler-bundle": "6.2.*",
+        "symfony/stopwatch": "6.3.*",
+        "symfony/web-profiler-bundle": "6.3.*",
         "timeweb/phpstan-enum": "^3.1",
         "zenstruck/foundry": "^1.31"
     },
@@ -139,7 +139,7 @@
     "extra": {
         "symfony": {
             "allow-contrib": false,
-            "require": "6.2.*"
+            "require": "6.3.*"
         },
         "phpstan": {
             "includes": [

+ 0 - 26
config/api_platform/Access/access.yaml

@@ -1,35 +1,9 @@
 resources:
   App\Entity\Access\Access:
     - operations:
-        ApiPlatform\Metadata\GetCollection: ~
-
         ApiPlatform\Metadata\Get:
           security: '(is_granted("ROLE_USERS_VIEW") and object.getOrganization().getId() == user.getOrganization().getId()) or (object.getId() == user.getId())'
 
         ApiPlatform\Metadata\Put:
           security: 'is_granted("ROLE_USERS") or (object.getId() == user.getId())'
 
-        ApiPlatform\Metadata\Delete: ~
-
-    - operations:
-        ApiPlatform\Metadata\GetCollection:
-          name: 'cget_students'
-          uriTemplate: '/students'
-          security: 'is_granted("ROLE_USERS_VIEW")'
-
-    - operations:
-        ApiPlatform\Metadata\GetCollection:
-          name: 'cget_access_person_ref'
-          uriTemplate: '/access_people'
-          normalization_context:
-            groups: [ 'access_people_ref' ]
-
-    - operations:
-        ApiPlatform\Metadata\Get:
-          name: 'get_access_address'
-          uriTemplate: '/access_addresses/{id}'
-          requirements:
-            id: '\d+'
-          normalization_context:
-            groups: [ 'access_address', 'address' ] ]
-          security: 'object.getOrganization().getId() == user.getOrganization().getId()'

+ 2 - 2
config/services.yaml

@@ -48,11 +48,11 @@ services:
     # To use the test fixtures
     App\Tests\Fixture\:
         resource: '%kernel.project_dir%/tests/Fixture/*'
-        
+
     #########################################
     ##  TAG Services ##
     _instanceof:
-        App\Doctrine\Access\AccessExtensionInterface:
+        App\Doctrine\Access\AdditionalExtension\AdditionalAccessExtensionInterface:
             tags: ['app.extensions.access']
         App\Service\Access\OptionalsRolesInterface:
             tags: ['app.optionalsroles']

+ 3 - 0
doc/internal_requests.md

@@ -46,6 +46,9 @@ Les appels à cette route ne sont autorisés que si :
 
 Si ces deux conditions ne sont pas remplies, la requête est rejetée, et ce même si l'utilisateur est authentifié.
 
+Les routes internal sont configurées ici : `config/packages/security.yaml`
+
+
 
 ### Valider le fonctionnement
 

+ 186 - 0
doc/security.md

@@ -0,0 +1,186 @@
+# Security
+
+## Authentification Symfony
+
+### Fonctionnement de base
+
+L'authentification se fait via une requête POST envoyée à l'adresse `/login_check` avec le body suivant : 
+
+    {
+        "username": "login",
+        "password": "password"
+    }
+
+En cas de succès, la requête renvoie un token qui servira ensuite à l'utilisateur à s'identifier.
+
+Les requêtes suivantes devront posséder les headers suivants : 
+
+* `x-accessid` : l'id de l'utilisateur (ou Access)
+* `authorization` : une chaine de caractères de la forme "BEARER XXXXX", où XXXXX est le token retourné par la requête de login
+
+### Connexion Switch
+
+Certains utilisateurs (admins, familles) peuvent prendre le rôle d'un autre utilisateur via la connexion switch.
+
+Pour ce faire, un nouveau header doit être ajouté aux requêtes : 
+
+* `x-switch-user`: l'id de l'utilisateur dont on veut prendre le rôle
+
+
+
+## Roles et Modules
+
+Les droits d'un utilisateur sont conditionnés à différents critères, dont : 
+
+* Les **modules** que possède l'organisation à laquelle il est appartient
+* Les **rôles** de cet utilisateur au sein de cette organisation
+
+On peut obtenir la liste des modules de l'organisation et des rôles de l'utilisateur actif en son sein au moyen de la 
+requête : `/api/my_profile`
+
+
+### Modules
+
+Les modules d'une organisation dépendent du produit acheté par celle-ci et des éventuels modules complémentaires. Ces deux 
+informations sont stoquées dans la table `Settings`.
+
+Le fichier `config/opentalent/products.yaml` définit :
+
+- L'appartenance des _modules_ aux _products_
+- L'appartenance des _entities_ aux _modules_
+
+De plus, le fichier `config/opentalent/modulesbyconditions.yaml` complète cette configuration en définissant des modules
+présentant des conditions particulières (appartenance à la CMF en particulier)
+
+A chaque requête effectuée, la classe `\App\Security\Voter\ModuleVoter` vérifie si la ressource demandée appartient à un
+module possédé par l'organisation de l'utilisateur. Si ce n'est pas le cas, une erreur `AccessDeniedHttpException` est levée.
+
+
+## Différentes méthodes de sécurisation
+
+### Les annotations Api-Platform
+
+La sécurité des ApiResources peut être définie de manière globale pour la ressource, ou pour chaque opération (Get, 
+GetCollection, Put, Post, Delete) via les annotations.
+
+Exemple : 
+
+    #[ApiResource(
+        operations: [
+            new Get(
+                security: '(is_granted("ROLE_ORGANIZATION_VIEW") or is_granted("ROLE_ORGANIZATION")) and object.getOrganization().getId() == user.getOrganization().getId()'
+            ),
+            new Put(
+                security: 'is_granted("ROLE_ORGANIZATION") and object.getOrganization().getId() == user.getOrganization().getId()'
+            )
+        ],
+    )]
+
+Dans certains cas plus complexes (ex: Access), cette configuration peut être déplacée dans un fichier de configuration
+situé dans le répertoire `~/config/api_platform/` et portant le nom de la ressource.
+
+> Voir plus : https://api-platform.com/docs/core/security/
+
+
+### Voters
+
+Les voters permettent de contrôler l'accès à certaines ressources, selon le type d'opération.
+
+Ils implémentent essentiellement deux méthodes : `supports` et `voteOnAttribute` qui prennent en paramètres la ressource 
+et le type d'opération. `supports` retourne `true` si le voter doit s'appliquer dans ce cas. Si oui, `voteOnAttribute`
+est appellée et retourne `true` si l'opération est autorisée.
+
+> TODO: quand faut-il retourner false et quand faut-il lever une AccessDeniedHttpException ?
+
+Pour qu'un Voter soit appellé, il faut configurer la sécurité de la ressource via son annotation et utiliser la méthode 
+`is_granted` combinée à une des constantes : 
+
+Exemple : 
+
+    #[ApiResource(
+        operations: [
+            new Get(security: "is_granted('READ', object)"),
+            new Put(security: "is_granted('EDIT', object)"),
+            new Post(security: "is_granted('CREATE', object)"),
+            new Delete(security: "is_granted('DELETE', object)"),
+        ]
+    )]
+    class File
+
+    
+
+Les voters custom sont enregistrés dans le répertoire `src/Security/Voter`.
+
+> Voir plus : https://symfony.com/doc/current/security/voters.html
+
+
+### Extensions Doctrine
+
+Le framework Api-Platform propose un système d'extensions doctrine pour ajouter des filtres automatiques aux opérations
+réalisées sur des ressources voulues.
+
+Concrètement, l'extension ajoutera une condition au `WHERE` de la requête SQL.
+
+Une extension implémente trois méthodes : 
+
+* `applyToCollection`
+* `applyToItem`
+* `addWhere`
+
+Les deux premières permettent de tester si l'extension doit s'appliquer à la ressource.
+
+La dernière ajoute la condition en question.
+
+> Voir : https://api-platform.com/docs/core/extensions/
+
+
+### Quand utiliser les annotations, un Voter ou une Extension Doctrine ?
+
+Si la sécurité doit pouvoir s'appliquer à une collection et filtrer le résultat de celle-ci (exemple: les notifications 
+d'un utilisateur) : on utilisera une _Extension Doctrine_
+
+Sinon, si les conditions d'accès peuvent s'écrire facilement avec l'expression language des annotations
+api-platform, on utilisera les _annotations Api-platform_.
+
+Si aucune des conditions précédentes n'est remplie, on utilisera un _Voter Symfony_.
+
+
+## Internal Requests
+
+Certaines routes de la forme `/internal/...` permettent d'accepter des requêtes sans que le client ne
+soit authentifié (cf `config/packages/security.yaml`), en se basant sur un contrôle de l'IP et un token. 
+
+> Voir: https://gitlab.2iopenservice.com/opentalent/ap2i/-/blob/develop/doc/internal_requests.md
+
+
+
+## Cas particuliers 
+
+### Les Fichiers
+
+Le client est autorisé à consulter l'enregistrement si l'une de ces conditions est vraie :
+
+* pas de date de disponibilité ou date de disponibilité antérieure à Now
+* date de disponibilité postérieure à Now, mais utilisateur a le rôle ROLE_BILLACCOUNTING et le fichier est de type BILL (facture)
+* visibilité est 'EVERYBODY' 
+* requête interne
+* est connecté et fait partie des AccessPersons du fichier
+* est connecté et est le propriétaire du fichier
+* est connecté et a le rôle ROLE_FILE et fichier appartient à l'utilisateur ou à son organisation
+* est connecté et fichier a des accessRoles et user a un des roles présents dans accessRoles et fichier appartient à l'utilisateur ou à son organisation (deprecated)
+
+
+Est autorisé à éditer si :
+
+* pas de date de disponibilité ou date de disponibilité antérieure à Now
+* date de disponibilité postérieure à Now, mais utilisateur a le rôle ROLE_BILLACCOUNTING et le fichier est de type BILL (facture)
+* est connecté et a le rôle ROLE_FILE et fichier appartient à l'utilisateur ou à son organisation
+* est connecté et est le propriétaire du fichier
+
+Est autorisé à créer si :
+
+* est connecté
+
+Est autorisé à supprimer si :
+
+* est connecté et est autorisé à éditer

+ 1 - 14
src/ApiResources/Access/AdminAccess.php

@@ -15,20 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Classe resource qui contient les champs d'un compte admin
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            uriTemplate: '/admin/{id}',
-            defaults: ['id' => 0],
-            provider: AdminAccessProvider::class
-        ),
-        new Put(
-            uriTemplate: '/admin/{id}',
-            defaults: ['id' => 0],
-            processor: AdminAccessProcessor::class
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 class AdminAccess implements ApiResourcesInterface
 {
     #[ApiProperty(identifier: true)]

+ 10 - 9
src/ApiResources/Cotisation/Cotisation.php

@@ -12,16 +12,17 @@ use Symfony\Component\Validator\Constraints as Assert;
 
 /**
  * Classe resource qui contient les informations des cotisations de la 5.9
+ *
+ * Security :
+ *   * @see App\Security\Voter\CotisationVoter
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            uriTemplate: '/cotisations/{organizationId}',
-            security: 'is_granted("ROLE_COTISATION", object) and object.getOrganizationId() == user.getOrganization().getId()',
-            provider: CotisationProvider::class
-        )
-    ]
-)]
+#[ApiResource(operations: [
+    new Get(
+        uriTemplate: '/cotisations/{organizationId}',
+        security: 'is_granted("ROLE_COTISATION", object) and object.getOrganizationId() == user.getOrganization().getId()',
+        provider: CotisationProvider::class
+    )
+])]
 class Cotisation implements ApiResourcesInterface
 {
     #[ApiProperty(identifier: true)]

+ 37 - 7
src/Doctrine/AbstractExtension.php

@@ -11,11 +11,13 @@ use Doctrine\ORM\QueryBuilder;
 use Exception;
 
 /**
- * Classe ... qui ...
+ * Base class for custom doctrine extensions
  */
 abstract class AbstractExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
 {
     /**
+     * Called by doctrine when getting a collection
+     *
      * @param QueryBuilder $queryBuilder
      * @param QueryNameGeneratorInterface $queryNameGenerator
      * @param string $resourceClass
@@ -26,10 +28,12 @@ abstract class AbstractExtension implements QueryCollectionExtensionInterface, Q
      */
     public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
     {
-        $this->addWhere($queryBuilder, $resourceClass);
+        $this->apply($queryBuilder, $resourceClass, $operation);
     }
 
     /**
+     * Called by doctrine when getting an item
+     *
      * @param QueryBuilder $queryBuilder
      * @param QueryNameGeneratorInterface $queryNameGenerator
      * @param string $resourceClass
@@ -41,11 +45,37 @@ abstract class AbstractExtension implements QueryCollectionExtensionInterface, Q
      */
     public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
     {
-        $this->addWhere($queryBuilder, $resourceClass);
+        $this->apply($queryBuilder, $resourceClass, $operation);
     }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        throw new Exception('need override addWhere function', 500);
+    /**
+     * Generic application of the extension
+     *
+     * @param $queryBuilder
+     * @param $resourceClass
+     * @return void
+     */
+    protected function apply(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void {
+        if (!$this->supports($resourceClass, $operation)) {
+            return;
+        }
+        $this->addWhere($queryBuilder, $resourceClass, $operation);
     }
-}
+
+    /**
+     * Returns true if the extension supports the given resource
+     *
+     * @param string $resourceClass
+     * @return bool
+     */
+    abstract protected function supports(string $resourceClass, ?Operation $operation): bool;
+
+    /**
+     * Add one or more filters to the query
+     *
+     * @param QueryBuilder $queryBuilder
+     * @param string $resourceClass
+     * @return void
+     */
+    abstract protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void;
+}

+ 0 - 11
src/Doctrine/Access/AccessExtensionInterface.php

@@ -1,11 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Doctrine\Access;
-
-use Doctrine\ORM\QueryBuilder;
-
-interface AccessExtensionInterface{
-    public function support(string $name): bool;
-    public function addWhere(QueryBuilder $queryBuilder): void;
-}

+ 15 - 0
src/Doctrine/Access/AdditionalExtension/AdditionalAccessExtensionInterface.php

@@ -0,0 +1,15 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Doctrine\Access\AdditionalExtension;
+
+use Doctrine\ORM\QueryBuilder;
+
+/**
+ * Complément d'extension pour CurrentAccessExtension.
+ * Les classes implémentant cette interface seront appellées par le service CurrentAccessExtensionIterator
+ */
+interface AdditionalAccessExtensionInterface{
+    public function support(string $name): bool;
+    public function addWhere(QueryBuilder $queryBuilder): void;
+}

+ 6 - 3
src/Doctrine/Access/Extensions/DateTimeConstraintExtension.php → src/Doctrine/Access/AdditionalExtension/DateTimeConstraintExtensionAdditional.php

@@ -1,13 +1,16 @@
 <?php
 declare(strict_types=1);
 
-namespace App\Doctrine\Access\Extensions;
+namespace App\Doctrine\Access\AdditionalExtension;
 
-use App\Doctrine\Access\AccessExtensionInterface;
 use Doctrine\ORM\QueryBuilder;
 use Symfony\Component\HttpFoundation\RequestStack;
 
-class DateTimeConstraintExtension implements AccessExtensionInterface {
+/**
+ * Contrainte supplémentaire pour CurrentAccessExtension.
+ * Ajoute un critère de dates
+ */
+class DateTimeConstraintExtensionAdditional implements AdditionalAccessExtensionInterface {
     public function __construct(
         private RequestStack $requestStack
     ){

+ 7 - 4
src/Doctrine/Access/Extensions/StudentsExtension.php → src/Doctrine/Access/AdditionalExtension/StudentsExtensionAdditional.php

@@ -1,12 +1,15 @@
 <?php
 declare(strict_types=1);
 
-namespace App\Doctrine\Access\Extensions;
+namespace App\Doctrine\Access\AdditionalExtension;
 
-use App\Doctrine\Access\AccessExtensionInterface;
 use Doctrine\ORM\QueryBuilder;
 
-class StudentsExtension implements AccessExtensionInterface {
+/**
+ * Contrainte supplémentaire pour CurrentAccessExtension.
+ *
+ */
+class StudentsExtensionAdditional implements AdditionalAccessExtensionInterface {
 
     public function support(string $name): bool
     {
@@ -16,4 +19,4 @@ class StudentsExtension implements AccessExtensionInterface {
     public function addWhere(QueryBuilder $queryBuilder): void
     {
     }
-}
+}

+ 6 - 32
src/Doctrine/Access/CurrentAccessExtension.php

@@ -7,6 +7,7 @@ use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
 use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
 use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
 use ApiPlatform\Metadata\Operation;
+use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Service\ServiceIterator\CurrentAccessExtensionIterator;
 use Doctrine\ORM\QueryBuilder;
@@ -16,7 +17,7 @@ use Symfony\Bundle\SecurityBundle\Security;
  * Class CurrentAccessExtension : Filtre de sécurité par défaut pour une resource Access
  * @package App\Doctrine\Access
  */
-final class CurrentAccessExtension  implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
+final class CurrentAccessExtension extends AbstractExtension
 {
     public function __construct(
         private Security $security,
@@ -24,39 +25,12 @@ final class CurrentAccessExtension  implements QueryCollectionExtensionInterface
     )
     { }
 
-    /**
-     * @param QueryBuilder $queryBuilder
-     * @param QueryNameGeneratorInterface $queryNameGenerator
-     * @param string $resourceClass
-     * @param Operation|null $operation
-     * @param mixed[] $context
-     * @return void
-     */
-    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
-    {
-        $this->addWhere($queryBuilder, $resourceClass, $operation?->getName());
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === Access::class;
     }
 
-    /**
-     * @param QueryBuilder $queryBuilder
-     * @param QueryNameGeneratorInterface $queryNameGenerator
-     * @param string $resourceClass
-     * @param list<int> $identifiers
-     * @param Operation|null $operation
-     * @param mixed[] $context
-     * @return void
-     */
-    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
+    public function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
     {
-        $this->addWhere($queryBuilder, $resourceClass, $operation?->getName());
-    }
-
-    public function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?string $operationName): void
-    {
-        if (Access::class !== $resourceClass) {
-            return;
-        }
-
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -65,6 +39,6 @@ final class CurrentAccessExtension  implements QueryCollectionExtensionInterface
             ->setParameter('current_organization', $currentUser->getOrganization())
         ;
 
-        $this->currentAccessExtensionIterator->addWhere($queryBuilder, $operationName);
+        $this->currentAccessExtensionIterator->addWhere($queryBuilder, $operation?->getName());
     }
 }

+ 7 - 7
src/Doctrine/Access/CurrentUserPersonalizedListExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Access;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\PersonalizedList;
 use Doctrine\ORM\QueryBuilder;
@@ -17,16 +18,15 @@ final class CurrentUserPersonalizedListExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (PersonalizedList::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === PersonalizedList::class;
+    }
 
-        /** @var PersonalizedList $currentUser */
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
         $queryBuilder->andWhere(sprintf('%s.access = :current_access', $rootAlias));
         $queryBuilder->setParameter('current_access', $currentUser);
     }
-}
+}

+ 7 - 6
src/Doctrine/Billing/CurrentResidenceAreaExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Billing;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Billing\ResidenceArea;
@@ -18,12 +19,12 @@ final class CurrentResidenceAreaExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (ResidenceArea::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === ResidenceArea::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentResidenceAreaExtension extends AbstractExtension
             ->setParameter('billingSetting', $currentUser->getOrganization()->getBillingSetting())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Booking/CurrentCoursesExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Booking;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Booking\Course;
@@ -18,15 +19,15 @@ final class CurrentCoursesExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === Course::class;
+    }
+
     /**
      * @todo : A la suite de la migration, il faut supprimer le where avec le discr.
      */
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
     {
-        if (Course::class !== $resourceClass) {
-            return;
-        }
-
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -37,4 +38,4 @@ final class CurrentCoursesExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 6 - 5
src/Doctrine/Core/AllowedAddressPostalExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Core;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Core\AddressPostal;
@@ -18,12 +19,12 @@ final class AllowedAddressPostalExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (AddressPostal::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === AddressPostal::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];

+ 7 - 6
src/Doctrine/Core/CurrentUserNotificationExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Core;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Core\Notification;
@@ -10,7 +11,7 @@ use Doctrine\ORM\QueryBuilder;
 use Symfony\Bundle\SecurityBundle\Security;
 
 /**
- * Class NotificationExtension : Filtre de sécurité par défaut pour une resource Notification
+ * Filtre de sécurité par défaut pour une resource Notification
  * @package App\Doctrine\Core
  */
 final class CurrentUserNotificationExtension extends AbstractExtension
@@ -18,15 +19,15 @@ final class CurrentUserNotificationExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === Notification::class;
+    }
+
     /**
      * @todo : A la suite de la migration, il faut supprimer le where avec le discr.
      */
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
     {
-        if (Notification::class !== $resourceClass) {
-            return;
-        }
-
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];

+ 7 - 6
src/Doctrine/Core/CurrentNotificationUserExtension.php → src/Doctrine/Core/CurrentUserNotificationUserExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Core;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Core\NotificationUser;
@@ -14,20 +15,20 @@ use Symfony\Bundle\SecurityBundle\Security;
  *
  * @package App\Doctrine\Core
  */
-final class CurrentNotificationUserExtension extends AbstractExtension
+final class CurrentUserNotificationUserExtension extends AbstractExtension
 {
     public function __construct(private Security $security)
     { }
 
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === NotificationUser::class;
+    }
+
     /**
      * @todo : A la suite de la migration, il faut supprimer le where avec le discr.
      */
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
     {
-        if (NotificationUser::class !== $resourceClass) {
-            return;
-        }
-
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];

+ 6 - 7
src/Doctrine/Education/CurrentCycleExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Education;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Education\Cycle;
@@ -18,13 +19,11 @@ final class CurrentCycleExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === Cycle::class;
+    }
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
     {
-        if (Cycle::class !== $resourceClass) {
-            return;
-        }
-
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -33,4 +32,4 @@ final class CurrentCycleExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Education/CurrentEducationNotationConfigExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Education;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Education\EducationNotationConfig;
@@ -18,12 +19,12 @@ final class CurrentEducationNotationConfigExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (EducationNotationConfig::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === EducationNotationConfig::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentEducationNotationConfigExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Education/CurrentEducationTimingExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Education;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Education\EducationTiming;
@@ -18,12 +19,12 @@ final class CurrentEducationTimingExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (EducationTiming::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === EducationTiming::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentEducationTimingExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Network/CurrentNetworkOrganizationExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Network;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Network\NetworkOrganization;
@@ -18,12 +19,12 @@ final class CurrentNetworkOrganizationExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (NetworkOrganization::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === NetworkOrganization::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentNetworkOrganizationExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Organization/CurrentOrganizationAddressPostalExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Organization;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Organization\OrganizationAddressPostal;
@@ -18,12 +19,12 @@ final class CurrentOrganizationAddressPostalExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (OrganizationAddressPostal::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === OrganizationAddressPostal::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentOrganizationAddressPostalExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 7 - 6
src/Doctrine/Organization/CurrentOrganizationArticleExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Organization;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Organization\OrganizationArticle;
@@ -18,12 +19,12 @@ final class CurrentOrganizationArticleExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (OrganizationArticle::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === OrganizationArticle::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];
@@ -32,4 +33,4 @@ final class CurrentOrganizationArticleExtension extends AbstractExtension
             ->setParameter('organization', $currentUser->getOrganization())
         ;
     }
-}
+}

+ 6 - 5
src/Doctrine/Organization/CurrentOrganizationExtension.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Doctrine\Organization;
 
+use ApiPlatform\Metadata\Operation;
 use App\Doctrine\AbstractExtension;
 use App\Entity\Access\Access;
 use App\Entity\Organization\Organization;
@@ -18,12 +19,12 @@ final class CurrentOrganizationExtension extends AbstractExtension
     public function __construct(private Security $security)
     { }
 
-    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
-    {
-        if (Organization::class !== $resourceClass) {
-            return;
-        }
+    public function supports(string $resourceClass, ?Operation $operation): bool {
+        return $resourceClass === Organization::class;
+    }
 
+    protected function addWhere(QueryBuilder $queryBuilder, string $resourceClass, ?Operation $operation): void
+    {
         /** @var Access $currentUser */
         $currentUser = $this->security->getUser();
         $rootAlias = $queryBuilder->getRootAliases()[0];

+ 6 - 1
src/Entity/Access/Access.php

@@ -65,8 +65,13 @@ use Symfony\Component\Serializer\Annotation\Groups;
 
 /**
  * Fais le lien entre une Person et une Organization
+ *
+ * Security :
+ *
+ *     @see ~/config/api_platform/Access/access.yaml
+ *     @see App\Doctrine\Access\CurrentAccessExtension
  */
-#[ApiResource] // Config in config/api_platform/Access/access.yaml
+#[ApiResource]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: AccessRepository::class)]
 #[ApiFilter(filterClass: BooleanFilter::class, properties: ['person.isPhysical'])]

+ 2 - 8
src/Entity/Access/AccessFamily.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class AccessFamily
@@ -102,4 +96,4 @@ class AccessFamily
         }
         return $this;
     }
-}
+}

+ 1 - 1
src/Entity/Access/OrganizationFunction.php

@@ -15,7 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Fonction d'un Access dans une Organization sur une période donnée
  */
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationFunctionRepository::class)]
 #[DateTimeConstraintAware(startDateFieldName: "startDate", endDateFieldName: "endDate")]

+ 6 - 1
src/Entity/Access/PersonalizedList.php

@@ -14,6 +14,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
 
 /**
  * Liste personnalisées
+ *
+ * Security :
+ *
+ *     @see \App\Doctrine\Access\CurrentUserPersonalizedListExtension
+ *
  */
 #[ApiResource(
     operations: [
@@ -25,7 +30,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
         )
     ],
     paginationEnabled: false
-)]
+)] // @see App\Doctrine\Access\CurrentUserPersonalizedListExtension
 #[ORM\Entity(repositoryClass: PersonalizedListRepository::class)]
 class PersonalizedList
 {

+ 2 - 8
src/Entity/Billing/AccessBilling.php

@@ -14,13 +14,7 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class AccessBilling
@@ -179,4 +173,4 @@ class AccessBilling
         $this->access = $access;
         return $this;
     }
-}
+}

+ 1 - 1
src/Entity/Billing/AccessIntangible.php

@@ -14,7 +14,7 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Enregistrement d'un produit à facturer par un Access
  */
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: AccessIntangibleRepository::class)]
 class AccessIntangible extends AbstractBillingIntangible

+ 1 - 7
src/Entity/Billing/AccessPayer.php

@@ -17,13 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
  * Fais le lien entre l'Access qui règle la facture et l'Access concerné
  *
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccessPayer().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Table(name: 'BillingPayer')]
 #[ORM\Entity(repositoryClass: AccessPayerRepository::class)]

+ 2 - 8
src/Entity/Billing/Bill.php

@@ -13,13 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Bill, et supprimer l'attribut discr.
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'BillAccounting')]
@@ -42,4 +36,4 @@ class Bill extends BillAccounting implements BillAccountingInterface
         $this->access = $access;
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Billing/BillAccounting.php

@@ -16,13 +16,7 @@ use Doctrine\Common\Collections\Collection;
  * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table BillAccounting, et supprimer l'attribut discr.
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class BillAccounting

+ 2 - 8
src/Entity/Billing/BillCredit.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'BillAccounting')]
@@ -43,4 +37,4 @@ class BillCredit extends BillAccounting implements BillAccountingInterface
         $this->access = $access;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Billing/BillLine.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class BillLine
@@ -91,4 +85,4 @@ class BillLine
         $this->equipmentLoan = $equipmentLoan;
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Billing/BillingIntangibleExcludeDate.php

@@ -12,13 +12,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class BillingIntangibleExcludeDate

+ 1 - 5
src/Entity/Billing/BillingSetting.php

@@ -13,11 +13,7 @@ use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 use JetBrains\PhpStorm\Pure;
 
-#[ApiResource(
-    operations: [
-        new Get()
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: BillingSettingRepository::class)]
 class BillingSetting

+ 2 - 8
src/Entity/Billing/EducationalProjectPayer.php

@@ -15,13 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getEducationalProjectPayer().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Table(name: 'BillingPayer')]
 #[ORM\Entity]
@@ -67,4 +61,4 @@ class EducationalProjectPayer
         $this->educationalProjectReceiver = $educationalProjectReceiver;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Billing/FamilyQuotient.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getBillingSetting().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class FamilyQuotient
@@ -117,4 +111,4 @@ class FamilyQuotient
         }
         return $this;
     }
-}
+}

+ 5 - 18
src/Entity/Billing/ResidenceArea.php

@@ -19,25 +19,12 @@ use Doctrine\Common\Collections\Collection;
 
 /**
  * Zone de résidence d'un Access, telle que définie par l'Organization
+ *
+ * Security :
+ *
+ *     @see \App\Doctrine\Billing\CurrentResidenceAreaExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\') and object.getBillingSetting().getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'object.getBillingSetting().getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Delete(
-            security: 'object.getBillingSetting().getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        ),
-        new Post()
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
-)]
+#[ApiResource(operations: [])] // @see App\Doctrine\Billing\CurrentResidenceAreaExtension
 //#[Auditable]
 #[BillingSettingDefaultValue(fieldName: "billingSetting")]
 #[ORM\Entity(repositoryClass: ResidenceAreaRepository::class)]

+ 2 - 8
src/Entity/Booking/Attendance.php

@@ -15,13 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Attendance
@@ -114,4 +108,4 @@ class Attendance
         }
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Booking/AttendanceBooking.php

@@ -12,13 +12,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class AttendanceBooking

+ 5 - 9
src/Entity/Booking/Course.php

@@ -25,15 +25,11 @@ use Doctrine\Common\Collections\Collection;
  * @todo : migration table tag_booking
  *
  * Classe Course qui permet de gérer les cours de la structure.
+ *
+ * Security :
+ *   * @see App\Doctrine\Booking\CurrentCoursesExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_COURSE_VIEW\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection()
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: CourseRepository::class)]
 #[ORM\Table(name: 'Booking')]
@@ -401,4 +397,4 @@ class Course extends AbstractBooking
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Booking/EducationalProject.php

@@ -24,13 +24,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @todo : migration table tag_booking
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Table(name: 'Booking')]
 #[ORM\Entity]
@@ -461,4 +455,4 @@ class EducationalProject extends AbstractBooking
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Booking/Event.php

@@ -23,13 +23,7 @@ use Symfony\Component\Validator\Constraints as Assert;
  * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Event, et supprimer l'attribut discr.
  * @todo : migration table tag_booking
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Booking')]

+ 2 - 8
src/Entity/Booking/EventUser.php

@@ -12,13 +12,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class EventUser
@@ -60,4 +54,4 @@ class EventUser
         $this->guest = $guest;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Booking/Examen.php

@@ -22,13 +22,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @todo : migration table tag_booking
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Booking')]
@@ -324,4 +318,4 @@ class Examen extends AbstractBooking
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Booking/OrganizationHoliday.php

@@ -16,13 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Booking')]
@@ -92,4 +86,4 @@ class OrganizationHoliday extends AbstractBooking
         $this->organization = $organization;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Booking/PersonHoliday.php

@@ -16,13 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Booking')]
@@ -91,4 +85,4 @@ class PersonHoliday extends AbstractBooking
         $this->access = $access;
         return $this;
     }
-}
+}

+ 1 - 1
src/Entity/Core/AbstractInformation.php

@@ -26,7 +26,7 @@ use Symfony\Component\Serializer\Annotation\Context;
  *
  * Classe Notification. qui permet de gérer les notifications aux utilisateurs.
  */
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Information')]

+ 7 - 8
src/Entity/Core/AddressPostal.php

@@ -17,14 +17,13 @@ use Doctrine\ORM\Mapping as ORM;
 use App\Entity\Person\PersonAddressPostal;
 use Symfony\Component\Serializer\Annotation\Groups;
 
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\') and object.getOrganizationAddressPostal().getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection()
-    ]
-)]
+/**
+ * Adresse postale d'une organisation ou d'une personne
+ *
+ * Security :
+ *   * @see App\Doctrine\Core\AllowedAddressPostalExtension
+ */
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: AddressPostalRepository::class)]
 class AddressPostal

+ 4 - 15
src/Entity/Core/BankAccount.php

@@ -21,22 +21,11 @@ use Symfony\Component\Validator\Constraints as Assert;
 
 /**
  * Données bancaire d'une Person ou d'une Organization
+ *
+ * Security :
+ *   * @see App\Security\Voter\BankAccountVoter
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted("BANK_ACCOUNT_READ", object)'
-        ),
-        new Put(
-            security: 'is_granted("BANK_ACCOUNT_EDIT", object)'
-        ),
-        new Delete(
-            security: 'is_granted("BANK_ACCOUNT_DELETE", object)'
-        ),
-        new Post(),
-        new GetCollection()
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: BankAccountRepository::class)]
 class BankAccount

+ 4 - 15
src/Entity/Core/ContactPoint.php

@@ -25,22 +25,11 @@ use App\Validator\Core as OpentalentAssert;
 
 /**
  * Données de contact d'une Person ou d'une Organization ou d'un lieu
+ *
+ * Security :
+ *   * @see App\Security\Voter\ContactPointVoter
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted("CONTACT_POINT_READ", object)'
-        ),
-        new Put(
-            security: 'is_granted("CONTACT_POINT_EDIT", object)'
-        ),
-        new Delete(
-            security: 'is_granted("CONTACT_POINT_DELETE", object)'
-        ),
-        new Post(),
-        new GetCollection()
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: ContactPointRepository::class)]
 #[OpentalentAssert\ContactPoint]

+ 46 - 6
src/Entity/Core/File.php

@@ -5,6 +5,8 @@ namespace App\Entity\Core;
 
 use ApiPlatform\Metadata\Put;
 use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Delete;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\AccessWish\DocumentWish;
 use App\Entity\Booking\Event;
@@ -28,10 +30,18 @@ use Symfony\Component\Serializer\Annotation\Groups;
 use Symfony\Component\Validator\Constraints as Assert;
 use App\Enum\Core\FileStatusEnum;
 
+/**
+ * Fichier, généré ou uploadé, appartenant à une organisation ou à une personne.
+ *
+ * Security :
+ *   * @see App\Security\Voter\FileVoter
+ */
 #[ApiResource(
     operations: [
-        new Get(),
-        new Put()
+        new Get(security: "is_granted('READ', object)"),
+        new Put(security: "is_granted('EDIT', object)"),
+        new Post(security: "is_granted('CREATE', object)"),
+        new Delete(security: "is_granted('DELETE', object)")
     ]
 )]
 //#[Auditable]
@@ -172,10 +182,14 @@ class File
 
     //    #[ORM\Column]
     //    private ?int $eventReport_id;
-    //
-    //    #[ORM\Column]
-    //    private ?\DateTime $availabilityDate;
-    //
+
+    /**
+     * TODO: complete
+     * @var DateTime|null
+     */
+    #[ORM\Column]
+    private ?\DateTime $availabilityDate;
+
     //    #[ORM\Column]
     //    private ?int $documentWish_id;
     //
@@ -187,6 +201,7 @@ class File
     //
     //    #[ORM\Column]
     //    private ?int $work_id;
+
     #[ORM\ManyToMany(targetEntity: Person::class, inversedBy: 'personFiles')]
     #[Groups(['file_person'])]
     private Collection $accessPersons;
@@ -367,6 +382,15 @@ class File
         return $this;
     }
 
+    /**
+     * A priori inutilisé
+     * @deprecated
+     * @return Collection
+     */
+    public function getAccessRoles(): Collection {
+        return new ArrayCollection([]);
+    }
+
     /**
      * @return string
      */
@@ -604,6 +628,22 @@ class File
         return $this;
     }
 
+    /**
+     * @return DateTime|null
+     */
+    public function getAvailabilityDate(): ?DateTime
+    {
+        return $this->availabilityDate;
+    }
+
+    /**
+     * @param DateTime|null $availabilityDate
+     */
+    public function setAvailabilityDate(?DateTime $availabilityDate): void
+    {
+        $this->availabilityDate = $availabilityDate;
+    }
+
     /**
      * @return Collection<int, Person>
      */

+ 4 - 1
src/Entity/Core/Notification.php

@@ -24,7 +24,10 @@ use Symfony\Component\Serializer\Annotation\Context;
 /**
  * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Notification, et supprimer l'attribut discr.
  *
- * Classe Notification. qui permet de gérer les notifications aux utilisateurs.
+ * Notification à un utilisateur
+ *
+ * Security :
+ *   * @see App\Doctrine\Core\CurrentUserNotificationExtension
  */
 #[ApiResource(
     operations: [

+ 3 - 0
src/Entity/Core/NotificationUser.php

@@ -16,6 +16,9 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Les NotificationUser permettent de garder la trace des notifications et des tips
  * qui ont été lues par les utilisateurs
+ *
+ * Security :
+ *   * @see App\Doctrine\Core\CurrentUserNotificationUserExtension
  */
 #[ApiResource(
     operations: [

+ 2 - 8
src/Entity/Core/Tagg.php

@@ -41,13 +41,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Tagg
@@ -863,4 +857,4 @@ class Tagg
         }
         return $this;
     }
-}
+}

+ 1 - 10
src/Entity/Core/Tips.php

@@ -14,16 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
  * Classe ... qui ...
  */
 //#[Auditable]
-#[ApiResource(
-    operations: [
-        new Get(),
-        new GetCollection(
-            paginationMaximumItemsPerPage: 20,
-            paginationClientItemsPerPage: true,
-            order: ['id' => 'DESC']
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Tips extends AbstractInformation

+ 1 - 7
src/Entity/Donor/Donor.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Partenaire / Sponsor de la structure; les logo des donors apparaissent sur le site web de la structure
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Donor

+ 2 - 8
src/Entity/Education/CriteriaNotation.php

@@ -16,13 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class CriteriaNotation
@@ -182,4 +176,4 @@ class CriteriaNotation
         $this->noteMax = $noteMax;
         return $this;
     }
-}
+}

+ 4 - 14
src/Entity/Education/Cycle.php

@@ -19,21 +19,11 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Enum des cycles éducatifs, utilisés par les EducationCurriculum
  * NB: le nombre de cycles est fixé à 6, mais chaque Organization peut en modifier le label
+ *
+ * Security :
+ *   * @see App\Doctrine\Education\CurrentCycleExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        )
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: CycleRepository::class)]
 class Cycle

+ 2 - 8
src/Entity/Education/Education.php

@@ -17,13 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getEducationCategory().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Education
@@ -275,4 +269,4 @@ class Education
         $this->educationNotationConfig = $educationNotationConfig;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Education/EducationCategory.php

@@ -16,13 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class EducationCategory
@@ -87,4 +81,4 @@ class EducationCategory
         }
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Education/EducationCurriculum.php

@@ -15,13 +15,7 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Curriculum éducatif; composé d'un cycle, d'une année et d'un niveau
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getEducation().getEducationCategory().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: EducationCurriculumRepository::class)]
 class EducationCurriculum

+ 2 - 8
src/Entity/Education/EducationNotation.php

@@ -15,13 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getEducationStudent().getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: EducationNotationRepository::class)]
 class EducationNotation
@@ -119,4 +113,4 @@ class EducationNotation
         }
         return round($this->note, 2);
     }
-}
+}

+ 5 - 14
src/Entity/Education/EducationNotationConfig.php

@@ -21,21 +21,12 @@ use Symfony\Component\Validator\Constraints as Assert;
 
 /**
  * Configuration des grilles d'évaluation
+ *
+ * Security :
+ *   * @see App\Doctrine\Education\CurrentEducationNotationConfigExtension
+ *
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_PEDAGOGICS_ADMINISTRATION_VIEW\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_PEDAGOGICS_ADMINISTRATION_VIEW\')'
-        )
-    ],
-    security: 'is_granted(\'ROLE_PEDAGOGICS_ADMINISTRATION\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: EducationNotationConfigRepository::class)]
 #[OrganizationDefaultValue(fieldName: "organization")]

+ 2 - 8
src/Entity/Education/EducationStudent.php

@@ -16,13 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class EducationStudent
@@ -220,4 +214,4 @@ class EducationStudent
         $this->educationTiming = $educationTiming;
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Education/EducationTeacher.php

@@ -15,13 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class EducationTeacher
@@ -100,4 +94,4 @@ class EducationTeacher
         }
         return $this;
     }
-}
+}

+ 5 - 18
src/Entity/Education/EducationTiming.php

@@ -20,25 +20,12 @@ use Doctrine\Common\Collections\Collection;
 
 /**
  * Temps d'un enseignement
+ *
+ * Security:
+ *
+ *    @see \App\Doctrine\Education\CurrentEducationTimingExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Delete(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        ),
-        new Post()
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[OrganizationDefaultValue(fieldName: "organization")]
 #[ORM\Entity(repositoryClass: EducationTimingRepository::class)]

+ 1 - 7
src/Entity/Network/Network.php

@@ -18,13 +18,7 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Enum des différents réseaux auxquels peut appartenir une Organization
  */
-#[ApiResource(
-    operations: [
-        new Get(),
-        new GetCollection()
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: NetworkRepository::class)]
 class Network

+ 4 - 13
src/Entity/Network/NetworkOrganization.php

@@ -16,20 +16,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
 
 /**
  * Fait le lien entre une Organization et un Network
+ *
+ * Security :
+ *   * @see App\Doctrine\Network\CurrentNetworkOrganizationExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted("ROLE_ORGANIZATION_VIEW" and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        )
-    ],
-    normalizationContext: ['groups' => ['network']
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: NetworkOrganizationRepository::class)]
 #[DateTimeConstraintAware(startDateFieldName: "startDate", endDateFieldName: "endDate")]

+ 2 - 8
src/Entity/Organization/Activity.php

@@ -18,13 +18,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Activity
@@ -188,4 +182,4 @@ class Activity
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Organization/Jury.php

@@ -17,13 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Jury
@@ -144,4 +138,4 @@ class Jury
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 6 - 13
src/Entity/Organization/Organization.php

@@ -48,20 +48,13 @@ use Symfony\Component\Validator\Constraints as Assert;
 
 /**
  * Structure, organisation
+ *
+ * Security :
+ *   * @see App\Doctrine\Organization\CurrentOrganizationExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: '(is_granted("ROLE_ORGANIZATION_VIEW") or is_granted("ROLE_ORGANIZATION")) and object.getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'is_granted("ROLE_ORGANIZATION") and object.getId() == user.getOrganization().getId()'
-        ),
-        new Post(),
-        new GetCollection()
-    ],
-    processor: OrganizationProcessor::class
-)]
+#[ApiResource(operations: [
+    new Get(security: "object.getId() == user.getOrganization().getId()")
+])] //
 //#[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationRepository::class)]
 class Organization

+ 7 - 16
src/Entity/Organization/OrganizationAddressPostal.php

@@ -19,25 +19,16 @@ use Symfony\Component\Validator\Constraints as Assert;
 use Symfony\Component\Serializer\Annotation\Groups;
 use App\Validator\Organization as OpentalentAssert;
 
+/**
+ * Fait le lien entre une adresse postal et une organisation
+ *
+ * Security :
+ *   * @see App\Doctrine\Organization\CurrentOrganizationAddressPostalExtension
+ */
 #[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Delete(
-            security: 'object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        ),
-        new Post()
-    ],
     normalizationContext: ['groups' => ['address']],
     denormalizationContext: ['groups' => ['address']],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
+    operations: []
 )]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationAddressPostalRepository::class)]

+ 5 - 12
src/Entity/Organization/OrganizationArticle.php

@@ -12,19 +12,12 @@ use App\Repository\Organization\OrganizationArticleRepository;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
- * Fait le lien entre une Organization et un coup de projecteur
+ * Fait le lien entre une Organization et un article (ou coup de projecteur, tel qu'affiché dans l'annuaire des structures)
+ *
+ * Security :
+ *   *  @see App\Doctrine\Organization\CurrentOrganizationArticleExtension
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted("ROLE_ORGANIZATION_VIEW" and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(
-            security: 'is_granted(\'ROLE_ORGANIZATION_VIEW\')'
-        )
-    ],
-    security: 'is_granted(\'ROLE_ORGANIZATION\')'
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationArticleRepository::class)]
 class OrganizationArticle

+ 1 - 1
src/Entity/Organization/OrganizationLicence.php

@@ -16,7 +16,7 @@ use App\Entity\Access\Access;
 use App\Repository\Organization\OrganizationLicenceRepository;
 //use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\ORM\Mapping as ORM;
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: OrganizationLicenceRepository::class)]
 class OrganizationLicence

+ 1 - 1
src/Entity/Organization/Settings.php

@@ -21,7 +21,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Caractéristiques d'une Organization (produits, options...etc)
  */
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: SettingsRepository::class)]
 class Settings

+ 8 - 13
src/Entity/Organization/Subdomain.php

@@ -3,6 +3,7 @@ declare (strict_types=1);
 
 namespace App\Entity\Organization;
 
+use ApiPlatform\Action\NotFoundAction;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Put;
@@ -23,19 +24,13 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Sous-domaine enregistré par une organisation
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: '(is_granted("ROLE_ORGANIZATION_VIEW") or is_granted("ROLE_ORGANIZATION")) and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new Put(
-            security: 'is_granted("ROLE_ORGANIZATION") and object.getOrganization().getId() == user.getOrganization().getId()'
-        ),
-        new GetCollection(),
-        new Post()
-    ],
-    processor: SubdomainProcessor::class
-)]
+#[ApiResource(operations: [
+    new Get(
+        controller: NotFoundAction::class,
+        read: false,
+        output: false
+    ),
+])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: SubdomainRepository::class)]
 #[OrganizationDefaultValue(fieldName: "organization")]

+ 2 - 10
src/Entity/Organization/TypeOfPractice.php

@@ -19,15 +19,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Type des pratique d'une organisation
  */
-#[ApiResource(
-    operations: [
-        new Get(),
-        new GetCollection(
-            normalizationContext: ['groups' => ['read']]
-        )
-    ],
-    paginationEnabled: false
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: TypeOfPracticeRepository::class)]
 class TypeOfPractice
@@ -108,4 +100,4 @@ class TypeOfPractice
         }
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Person/Commission.php

@@ -16,13 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Commission
@@ -116,4 +110,4 @@ class Commission
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Person/CommissionMember.php

@@ -13,13 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class CommissionMember

+ 2 - 8
src/Entity/Person/CompanyPerson.php

@@ -13,13 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getAccess().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class CompanyPerson
@@ -65,4 +59,4 @@ class CompanyPerson
         $this->access = $access;
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Person/Person.php

@@ -26,13 +26,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 /**
  * Personne physique ou morale
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_USERS_VIEW\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: PersonRepository::class)]
 class Person implements UserInterface, PasswordAuthenticatedUserInterface

+ 1 - 1
src/Entity/Person/PersonActivity.php

@@ -22,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Lien entre une Person et une Activity
  */
-#[ApiResource]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: PersonActivityRepository::class)]
 class PersonActivity

+ 1 - 7
src/Entity/Person/PersonAddressPostal.php

@@ -16,13 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert;
 /**
  * Lien entre une Person et une AddressPostal
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_USERS_VIEW\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: PersonAddressPostalRepository::class)]
 class PersonAddressPostal

+ 1 - 7
src/Entity/Place/Place.php

@@ -25,13 +25,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Place')]

+ 2 - 8
src/Entity/Place/Room.php

@@ -20,13 +20,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getPlace().getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class Room
@@ -370,4 +364,4 @@ class Room
         }
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Product/Equipment.php

@@ -21,13 +21,7 @@ use Doctrine\ORM\Mapping as ORM;
  * @todo : migration table tag_product
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Product')]
@@ -398,4 +392,4 @@ class Equipment extends AbstractProduct
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 1 - 7
src/Entity/Product/EquipmentLoan.php

@@ -18,13 +18,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class EquipmentLoan

+ 2 - 8
src/Entity/Product/Intangible.php

@@ -20,13 +20,7 @@ use App\Entity\Education\EducationCurriculum;
  * @todo : migration table tag_product
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\') and object.getOrganization().getId() == user.getOrganization().getId()'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 #[ORM\Table(name: 'Product')]
@@ -150,4 +144,4 @@ class Intangible extends AbstractProduct
         $this->tags->removeElement($tag);
         return $this;
     }
-}
+}

+ 2 - 8
src/Entity/Product/IntangibleDiscountDetail.php

@@ -14,13 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
 /**
  * Classe ... qui ...
  */
-#[ApiResource(
-    operations: [
-        new Get(
-            security: 'is_granted(\'ROLE_ADMIN\')'
-        )
-    ]
-)]
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class IntangibleDiscountDetail
@@ -79,4 +73,4 @@ class IntangibleDiscountDetail
         $this->familyQuotient = $familyQuotient;
         return $this;
     }
-}
+}

+ 2 - 0
src/Enum/Core/FileTypeEnum.php

@@ -8,10 +8,12 @@ use MyCLabs\Enum\Enum;
  * @method static UNKNOWN()
  * @method static NONE()
  * @method static LICENCE_CMF()
+ * @method static BILL()
  */
 class FileTypeEnum extends Enum
 {
     private const UNKNOWN = 'UNKNOWN';
     private const NONE = 'NONE';
     private const LICENCE_CMF ='LICENCE_CMF';
+    private const BILL ='BILL';
 }

+ 19 - 0
src/Enum/Core/FileVisibilityEnum.php

@@ -0,0 +1,19 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Enum\Core;
+
+use MyCLabs\Enum\Enum;
+
+/**
+ * Statuts des fichiers
+ * @method static EVERYBODY()
+ * @method static NOBODY()
+ * @method static ONLY_ORGANIZATION()
+ */
+class FileVisibilityEnum extends Enum
+{
+    private const EVERYBODY = 'EVERYBODY';
+    private const NOBODY = 'NOBODY';
+    private const ONLY_ORGANIZATION = 'ONLY_ORGANIZATION';
+}

+ 187 - 0
src/Security/Voter/AbstractVoter.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace App\Security\Voter;
+
+use App\Entity\Access\Access;
+use App\Service\Access\Utils;
+use App\Service\Security\InternalRequestsService;
+use App\Service\Security\SwitchUser;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Authorization\Voter\Voter;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * Base class for custom Voters.
+ *
+ * This class also defines a default behavior for entity based voters (ex: FileVoter)
+ *
+ * @see doc/security.md
+ */
+abstract class AbstractVoter extends Voter
+{
+    protected const READ = 'READ';
+    protected const EDIT = 'EDIT';
+    protected const CREATE = 'CREATE';
+    protected const DELETE = 'DELETE';
+
+    /**
+     * The current user if any, else null; access it trough isUserLoggedIn or getUser methods
+     * @var Access|null
+     */
+    private ?Access $user = null;
+
+    /**
+     * The supported class name. Override it in subclass.
+     * Ex:
+     *     protected ?string $entityClass = File::class;
+     *
+     * @var string|null
+     */
+    protected static ?string $entityClass = null;
+
+    /**
+     * List of supported operations. Override it to restrict.
+     * @var array<string>
+     */
+    protected static array $allowedOperations = [
+        self::READ, self::EDIT, self::CREATE, self::DELETE
+    ];
+
+    public function __construct(
+        private Security $security,
+        protected Utils $accessUtils,
+        private InternalRequestsService $internalRequestsService,
+        EntityManagerInterface $em,
+        private SwitchUser $switchUser
+    ) {
+        /** @var Access $user */
+        $user = $this->security->getUser();
+
+        // <-- Special case of impersonated users: the switch user is not setup yet by symfony, we have to do it "manually"
+        $switchHeaderId = $_SERVER['HTTP_X_SWITCH_USER'] ?? null;
+        if ($switchHeaderId !== null) {
+            $switchAs = $em->find(Access::class, $switchHeaderId);
+            if (
+                $switchAs &&
+                (
+                    $this->security->isGranted('ROLE_ALLOWED_TO_SWITCH') ||
+                    $this->switchUser->isAllowedToSwitch($user, $switchAs)
+                )
+            ) {
+                $user = $switchAs;
+            }
+        }
+        // -->
+
+        // If the user is not anonymous, remember it
+        if ($user instanceof Access) {
+            $this->user = $user;
+        }
+    }
+
+    /**
+     * Default `supports` method, that uses self::entityClass and self::allowedOperations to determine if the voter
+     * supports the subject and attribute.
+     *
+     * @param string $attribute
+     * @param mixed $subject
+     * @return bool
+     */
+    protected function supports(string $attribute, mixed $subject): bool
+    {
+        if (static::$entityClass === null) {
+            throw new \RuntimeException('Setup the self::$entityClass property, or override the supports() method');
+        }
+
+        return $subject::class === static::$entityClass && in_array($attribute, static::$allowedOperations);
+    }
+
+    /**
+     * Default `voteOnAttribute` method, calling one of the `canXxxx()` method, according to the attribute.
+     *
+     * @param string $attribute
+     * @param mixed $subject
+     * @param TokenInterface $token
+     * @return bool
+     */
+    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
+    {
+        switch ($attribute) {
+            case self::READ:
+                return $this->canView($subject);
+            case self::EDIT:
+                return $this->canEdit($subject);
+            case self::CREATE:
+                return $this->canCreate($subject);
+            case self::DELETE:
+                return $this->canDelete($subject);
+        }
+
+        return false;
+    }
+
+    /**
+     * Does the client have the right to view this resource?
+     * @param object $subject
+     * @return bool
+     */
+    abstract protected function canView(object $subject): bool;
+
+    /**
+     * Does the client have the right to edit this resource?
+     * @param object $subject
+     * @return bool
+     */
+    abstract protected function canEdit(object $subject): bool;
+
+    /**
+     * Does the client have the right to create this resource?
+     *
+     * @param object $subject
+     * @return bool
+     */
+    abstract protected function canCreate(object $subject): bool;
+
+    /**
+     * Does the client have the right to delete this resource?
+     *
+     * @param object $subject
+     * @return bool
+     */
+    abstract protected function canDelete(object $subject): bool;
+
+    /**
+     * Returns the current logged in user
+     * @return Access
+     */
+    protected function getUser(): ?Access {
+        return $this->user;
+    }
+
+    /**
+     * Is the client an authenticated user ?
+     *
+     * @return bool
+     */
+    protected function isUserLoggedIn(): bool {
+        return $this->getUser() !== null;
+    }
+
+    /**
+     * Is the current request a valid internal request?
+     *
+     * @see doc/internal_requests.md
+     * @see doc/security.md
+     *
+     * @return bool
+     */
+    protected function isValidInternalRequest(): bool {
+        $clientIp = $_SERVER['REMOTE_ADDR'] ?? null;
+        $internalRequestToken = $_SERVER['HTTP_INTERNAL_REQUESTS_TOKEN'] ?? '';
+
+        return $this->internalRequestsService->isAllowed($clientIp, $internalRequestToken);
+    }
+
+}

+ 208 - 0
src/Security/Voter/Core/FileVoter.php

@@ -0,0 +1,208 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Security\Voter\Core;
+
+use App\Entity\Access\Access;
+use App\Entity\Core\File;
+use App\Enum\Core\FileVisibilityEnum;
+use App\Enum\Core\FileTypeEnum;
+use App\Security\Voter\AbstractVoter;
+use App\Service\Utils\DatesUtils;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+
+/**
+ * Contrôle l'accès à l'entité File
+ */
+class FileVoter extends AbstractVoter
+{
+    /**
+     * @inheritdoc
+     * @var string|null
+     */
+    protected static ?string $entityClass = File::class;
+
+    /**
+     * @inheritdoc
+     *
+     * @param $subject File
+     * @return boolean
+     */
+    protected function canView(object $subject): bool
+    {
+        // Le fichier n'est pas encore disponible
+        if(!$this->isAvailable($subject, $this->getUser())) {
+            // TODO: à revoir
+            return false;
+        }
+
+        // File has public visibility
+        if ($subject->getVisibility() === FileVisibilityEnum::EVERYBODY()->getValue()) {
+            return true;
+        }
+
+        // Is this an internal request?
+        if ($this->isValidInternalRequest()) {
+            return true;
+        }
+
+        // If the user is not logged in, the file is not available
+        if ($this->isUserLoggedIn()) {
+
+            // If the logged user is in the accessPersons of the File
+            if (
+                $subject->getAccessPersons()->contains($this->getUser()->getPerson())
+            ) {
+                return true;
+            }
+
+            // User is the owner of the file
+            if ($subject->getPerson() && $subject->getPerson()->getId() === $this->getUser()->getPerson()->getId()) {
+                return true;
+            }
+
+            // User has ROLE_FILE and owns the file or belongs to the organization who does
+            if (
+                $this->accessUtils->hasRole($this->getUser(), 'ROLE_FILE') &&
+                $this->isInFileOwningGroup($subject, $this->getUser())
+            ) {
+                return true;
+            }
+
+            // User has the requested role and owns the file or belongs to the organization who does (deprecated)
+            if (
+                $this->isInFileOwningGroupWithRole($subject, $this->getUser())
+            ) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @inheritdoc
+     *
+     * @param $subject File
+     * @return boolean
+     */
+    protected function canEdit(object $subject): bool
+    {
+        // Le fichier n'est pas encore disponible
+        if(!$this->isAvailable($subject, $this->getUser())) {
+            return false;
+        }
+
+        if ($this->isUserLoggedIn()) {
+
+            // User has ROLE_FILE and has access to the file
+            if (
+                $this->accessUtils->hasRole($this->getUser(), 'ROLE_FILE') &&
+                $this->isInFileOwningGroup($subject, $this->getUser())
+            ) {
+                return true;
+            }
+
+            // User is the owner of the file
+            if ($subject->getPerson() && $subject->getPerson()->getId() === $this->getUser()->getPerson()->getId()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @inheritdoc
+     *
+     * @param $subject File
+     * @return boolean
+     */
+    protected function canCreate(object $subject): bool
+    {
+        // An authenticated user can create a file
+        if ($this->isUserLoggedIn()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @inheritdoc
+     *
+     * @param $subject File
+     * @return boolean
+     */
+    protected function canDelete(object $subject): bool {
+        return $this->canEdit($subject);
+    }
+
+    /**
+     * Is the given user the owner of the file, or does he belong to the organization that owns it, and does the user
+     * have any of the required role to access this file?
+     *
+     * @param File $file
+     * @param Access $user
+     * @return bool
+     */
+    protected function isInFileOwningGroup(File $file, Access $user): bool {
+        $isOrganizationOwned = $file->getOrganization() && $file->getOrganization()->getId() === $user->getOrganization()->getId();
+        $isAccessOwned = $file->getPerson() && $file->getPerson()->getId() === $user->getPerson()->getId();
+
+        return $isOrganizationOwned || $isAccessOwned;
+    }
+
+    /**
+     * Is the given user the owner of the file, or does he belong to the organization that owns it, and does the user
+     * have any of the required role to access this file?
+     *
+     * @deprecated
+     * @param File $file
+     * @param Access $user
+     * @return bool
+     */
+    protected function isInFileOwningGroupWithRole(File $file, Access $user): bool {
+
+        $requiredRoles = $file->getAccessRoles();
+        if ($requiredRoles->count() === 0) {
+            return false;
+        }
+
+        $userHasRequiredRole = $file->getAccessRoles()->exists(
+            // TODO: implémenter une méthode hasAnyRoleAmong() côté accessUtils
+            fn($aR) => $this->accessUtils->hasRole($user, $aR)
+        );
+
+        return $this->isInFileOwningGroup($file, $user) && $userHasRequiredRole;
+    }
+
+    /**
+     * Is the file available now?
+     * Depends on the availabilityDate, the type of the file and the user
+     *
+     * @param $file File
+     * @return bool
+     * @throws \Exception
+     */
+    protected function isAvailable(File $file, ?Access $user): bool {
+
+        $today = DatesUtils::new();
+
+        if ($file->getAvailabilityDate() !== null && $file->getAvailabilityDate() > $today) {
+
+            //  Cas particulier de la liste des factures
+            // TODO: clarifier le ou les cas particulier(s) et les sortir de cette méthode (celle ci devrait tenir en deux lignes max)
+            if (
+                $user &&
+                $file->getType() === FileTypeEnum::BILL()->getValue() &&
+                $this->accessUtils->hasRole($user, 'ROLE_BILLACCOUNTING')
+            ) {
+                return true;
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+}

+ 2 - 1
src/Security/Voter/SwitchUserVoter.php

@@ -42,10 +42,11 @@ class SwitchUserVoter extends Voter
             return true;
         }
 
+        // TODO: on pourrait pas intégrer le isGranted à cette méthode?
         if ($this->switchUser->isAllowedToSwitch($user, $subject)) {
             return true;
         }
 
         return true;
     }
-}
+}

Неке датотеке нису приказане због велике количине промена