diff --git a/features/api/adherent_zone_based_roles.feature b/features/api/adherent_zone_based_roles.feature new file mode 100644 index 0000000000..af730ef064 --- /dev/null +++ b/features/api/adherent_zone_based_roles.feature @@ -0,0 +1,49 @@ +@api +Feature: + In order to manage adherent zone based roles + As a logged-in user granted with a specific feature + I should be able to create, read and update adherent zone based roles + + Scenario Outline: As a user granted with local scope, I can create an adherent zone based role + Given I am logged with "" via OAuth client "JeMengage Web" with scope "jemengage_admin" + And I send a "POST" request to "/api/v3/zone_based_role?scope=" with body: + """ + { + "adherent": "d0a0935f-da7c-4caa-b582-a8c2376e5158", + "type": "deputy", + "zones": ["e3efe6fd-906e-11eb-a875-0242ac150002"] + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the JSON should be equal to: + """ + { + "type": "deputy", + "adherent": { + "uuid": "d0a0935f-da7c-4caa-b582-a8c2376e5158", + "zones": [ + { + "uuid": "@uuid@", + "type": "department", + "code": "77", + "name": "Seine-et-Marne" + } + ] + }, + "uuid": "@uuid@", + "zones": [ + { + "uuid": "e3efe6fd-906e-11eb-a875-0242ac150002", + "code": "92", + "name": "Hauts-de-Seine", + "type": "department" + } + ] + } + """ + + Examples: + | user | scope | + | referent@en-marche-dev.fr | president_departmental_assembly | + | senateur@en-marche-dev.fr | delegated_08f40730-d807-4975-8773-69d8fae1da74 | diff --git a/features/api/my_teams.feature b/features/api/my_teams.feature index 89e83ec4c6..57cd97866b 100644 --- a/features/api/my_teams.feature +++ b/features/api/my_teams.feature @@ -340,7 +340,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ], "uuid": "e0da56db-c4c6-4aa4-ad8d-7e9505dfdd93" } @@ -425,7 +426,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ], "uuid": "@uuid@" } diff --git a/features/api/scopes.feature b/features/api/scopes.feature index 8852c3da4a..bacadca0fa 100644 --- a/features/api/scopes.feature +++ b/features/api/scopes.feature @@ -88,7 +88,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ] }, { @@ -222,7 +223,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ], "attributes": null, "delegated_access": null @@ -471,7 +473,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ], "delegated_access": { "delegator": { @@ -524,7 +527,8 @@ Feature: "statutory_message", "procurations", "actions", - "featurebase" + "featurebase", + "circonscriptions" ], "attributes": { "committees": [{ "name": "Comité des 3 communes", "uuid": "@uuid@" }], diff --git a/src/Entity/AdherentZoneBasedRole.php b/src/Entity/AdherentZoneBasedRole.php index 29f26ddeeb..e3ce9f1f84 100644 --- a/src/Entity/AdherentZoneBasedRole.php +++ b/src/Entity/AdherentZoneBasedRole.php @@ -2,14 +2,42 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\Put; use App\Adherent\Authorization\ZoneBasedRoleTypeEnum; use App\Collection\ZoneCollection; use App\Entity\Geo\Zone; use App\Scope\ScopeEnum; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Uuid; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +#[ApiResource( + operations: [ + new GetCollection( + uriTemplate: '/v3/zone_based_role', + ), + new Post( + uriTemplate: '/v3/zone_based_role', + ), + new Put( + uriTemplate: '/v3/zone_based_role/{uuid}', + requirements: ['uuid' => '%pattern_uuid%'] + ), + new Delete( + uriTemplate: '/v3/zone_based_role/{uuid}', + requirements: ['uuid' => '%pattern_uuid%'] + ), + ], + normalizationContext: ['groups' => ['zone_based_role_read']], + denormalizationContext: ['groups' => ['zone_based_role_write']], + validationContext: ['groups' => ['Default', 'zone_based_role_write']], + security: 'is_granted(\'REQUEST_SCOPE_GRANTED\', \'circonscriptions\')' +)] #[ORM\Entity] class AdherentZoneBasedRole { @@ -19,10 +47,12 @@ class AdherentZoneBasedRole #[Assert\Choice(choices: ZoneBasedRoleTypeEnum::ALL)] #[Assert\NotBlank] + #[Groups(['zone_based_role_read', 'zone_based_role_write'])] #[ORM\Column] private ?string $type; #[Assert\NotBlank] + #[Groups(['zone_based_role_read', 'zone_based_role_write'])] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\ManyToOne(targetEntity: Adherent::class, inversedBy: 'zoneBasedRoles')] private ?Adherent $adherent = null; @@ -90,6 +120,11 @@ public function setType(?string $type): void $this->type = $type; } + public function getAdherent(): ?Adherent + { + return $this->adherent; + } + public function setAdherent(Adherent $adherent): void { $this->adherent = $adherent; diff --git a/src/Entity/EntityIdentityTrait.php b/src/Entity/EntityIdentityTrait.php index 93f3ce9d7f..c76ce0f153 100644 --- a/src/Entity/EntityIdentityTrait.php +++ b/src/Entity/EntityIdentityTrait.php @@ -121,6 +121,8 @@ trait EntityIdentityTrait 'zone_read', 'tax_receipt:list', 'event_registration_list', + 'zone_based_role_read', + 'zone_based_role_write', ])] #[ORM\Column(type: 'uuid', unique: true)] protected $uuid; diff --git a/src/Entity/EntityZoneTrait.php b/src/Entity/EntityZoneTrait.php index e370ce6a59..59dc4c69f0 100644 --- a/src/Entity/EntityZoneTrait.php +++ b/src/Entity/EntityZoneTrait.php @@ -4,16 +4,30 @@ use App\Collection\ZoneCollection; use App\Entity\Geo\Zone; +use App\Validator\ZoneInScopeZones as AssertZoneInScopeZones; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; trait EntityZoneTrait { /** * @var ZoneCollection|Zone[] */ - #[Groups(['phoning_campaign_read', 'phoning_campaign_write', 'read_api', 'managed_users_list', 'managed_user_read'])] + #[Assert\All( + constraints: [new AssertZoneInScopeZones()], + groups: ['zone_based_role_write'], + )] + #[Groups([ + 'phoning_campaign_read', + 'phoning_campaign_write', + 'read_api', + 'managed_users_list', + 'managed_user_read', + 'zone_based_role_read', + 'zone_based_role_write', + ])] #[ORM\ManyToMany(targetEntity: Zone::class, cascade: ['persist'])] protected Collection $zones; diff --git a/src/Entity/Geo/GeoTrait.php b/src/Entity/Geo/GeoTrait.php index 11785beb30..18620bd27a 100644 --- a/src/Entity/Geo/GeoTrait.php +++ b/src/Entity/Geo/GeoTrait.php @@ -25,14 +25,14 @@ trait GeoTrait /** * @var string */ - #[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'read_api', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])] + #[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'read_api', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])] #[ORM\Column(unique: true)] private $code; /** * @var string */ - #[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])] + #[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])] #[ORM\Column] private $name; diff --git a/src/Entity/Geo/Zone.php b/src/Entity/Geo/Zone.php index 2be23e58ab..20292b71c4 100644 --- a/src/Entity/Geo/Zone.php +++ b/src/Entity/Geo/Zone.php @@ -95,14 +95,14 @@ class Zone implements GeoInterface, UuidEntityInterface * @var UuidInterface */ #[ApiProperty(identifier: true, openapiContext: ['type' => 'string', 'format' => 'uuid', 'example' => 'b4219d47-3138-5efd-9762-2ef9f9495084'])] - #[Groups(['zone_read', 'survey_write_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'survey_list_dc', 'survey_read_dc', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'formation_write', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'general_meeting_report_write', 'elected_mandate_write', 'action_read', 'department_site_write', 'committee:write', 'audience_write', 'profile_read'])] + #[Groups(['zone_read', 'survey_write_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'survey_list_dc', 'survey_read_dc', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'formation_write', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'general_meeting_report_write', 'elected_mandate_write', 'action_read', 'department_site_write', 'committee:write', 'audience_write', 'profile_read', 'zone_based_role_write', 'zone_based_role_read'])] #[ORM\Column(type: 'uuid', unique: true)] protected $uuid; /** * @var string */ - #[Groups(['zone_read', 'scope', 'read_api', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])] + #[Groups(['zone_read', 'scope', 'read_api', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])] #[ORM\Column] private $type; diff --git a/src/History/Listener/AdministratorActionHistorySubscriber.php b/src/History/Listener/AdministratorActionHistorySubscriber.php index f084ba976c..a03f0f01f1 100644 --- a/src/History/Listener/AdministratorActionHistorySubscriber.php +++ b/src/History/Listener/AdministratorActionHistorySubscriber.php @@ -87,7 +87,7 @@ public function onKernelResponse(ResponseEvent $event): void $request = $event->getRequest(); $routeName = $request->get('_route'); - if (!preg_match('/^admin_(.)+_export$/', $routeName)) { + if (!$routeName || !preg_match('/^admin_(.)+_export$/', $routeName)) { return; } diff --git a/src/Scope/FeatureEnum.php b/src/Scope/FeatureEnum.php index a658bea06d..074698d9b7 100644 --- a/src/Scope/FeatureEnum.php +++ b/src/Scope/FeatureEnum.php @@ -32,6 +32,7 @@ class FeatureEnum extends Enum public const PROCURATIONS = 'procurations'; public const ACTIONS = 'actions'; public const FEATUREBASE = 'featurebase'; + public const CIRCONSCRIPTIONS = 'circonscriptions'; public const ALL = [ self::DASHBOARD, @@ -60,6 +61,7 @@ class FeatureEnum extends Enum self::PROCURATIONS, self::ACTIONS, self::FEATUREBASE, + self::CIRCONSCRIPTIONS, ]; public const DELEGATED_ACCESSES_BY_DEFAULT = [ diff --git a/translations/messages+intl-icu.fr.yml b/translations/messages+intl-icu.fr.yml index d66bf36373..9970955f39 100644 --- a/translations/messages+intl-icu.fr.yml +++ b/translations/messages+intl-icu.fr.yml @@ -1160,6 +1160,7 @@ scope.feature.committee: Comités locaux scope.feature.designation: Désignation scope.feature.actions: Actions scope.feature.featurebase: 🗣 Featurebase ️ +scope.feature.circonscriptions: Gestion des circonscriptions scope.app.data_corner: DataCorner scope.app.jemarche: Je m'engage