From 594d3af0d04d7c777a36b2f351fb42897ad18068 Mon Sep 17 00:00:00 2001 From: Sabrina Demagny Date: Mon, 31 Mar 2025 14:18:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(plugins)=20add=20endpoint=20to=20list?= =?UTF-8?q?=20SIRET=20of=20active=20organizations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow access to AccountService with right scope to list SIRET of active communes --- CHANGELOG.md | 1 + src/backend/core/plugins/urls.py | 16 ++++ src/backend/people/urls.py | 4 + src/backend/plugins/la_suite/api/__init__.py | 1 + src/backend/plugins/la_suite/api/viewsets.py | 56 +++++++++++++ .../plugins/la_suite/tests/api/__init__.py | 1 + .../api/test_active_organizations_siret.py | 81 +++++++++++++++++++ src/backend/plugins/la_suite/urls.py | 14 ++++ 8 files changed, 174 insertions(+) create mode 100644 src/backend/core/plugins/urls.py create mode 100644 src/backend/plugins/la_suite/api/__init__.py create mode 100644 src/backend/plugins/la_suite/api/viewsets.py create mode 100644 src/backend/plugins/la_suite/tests/api/__init__.py create mode 100644 src/backend/plugins/la_suite/tests/api/test_active_organizations_siret.py create mode 100644 src/backend/plugins/la_suite/urls.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 997f757..5575bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(plugins) add endpoint to list siret of active organizations #771 - ✨(core) create AccountServiceAuthentication backend #771 - ✨(core) create AccountService model #771 - 🧱(helm) disable createsuperuser job by setting #863 diff --git a/src/backend/core/plugins/urls.py b/src/backend/core/plugins/urls.py new file mode 100644 index 0000000..ad25cd0 --- /dev/null +++ b/src/backend/core/plugins/urls.py @@ -0,0 +1,16 @@ +"""URLs for plugins""" + +from django.conf import settings +from django.urls import include, path + +plugins_urlpatterns = [] + +# Try to import and include URLs from each installed plugin +for app in settings.INSTALLED_PLUGINS: + try: + plugins_urlpatterns.append(path("", include(f"{app}.urls"))) + except (ImportError, AttributeError): + # Skip if app doesn't have urls.py + continue + +urlpatterns = plugins_urlpatterns diff --git a/src/backend/people/urls.py b/src/backend/people/urls.py index 7993364..e76367a 100644 --- a/src/backend/people/urls.py +++ b/src/backend/people/urls.py @@ -13,12 +13,15 @@ from drf_spectacular.views import ( ) from oauth2_provider import urls as oauth2_urls +from core.plugins import urls as plugin_urls + from debug import urls as debug_urls from . import api_urls, resource_server_urls API_VERSION = settings.API_VERSION + urlpatterns = ( [ path("admin/", admin.site.urls), @@ -26,6 +29,7 @@ urlpatterns = ( ] + api_urls.urlpatterns + resource_server_urls.urlpatterns + + plugin_urls.urlpatterns ) if settings.DEBUG: diff --git a/src/backend/plugins/la_suite/api/__init__.py b/src/backend/plugins/la_suite/api/__init__.py new file mode 100644 index 0000000..3cea276 --- /dev/null +++ b/src/backend/plugins/la_suite/api/__init__.py @@ -0,0 +1 @@ +"""API for La Suite plugin""" diff --git a/src/backend/plugins/la_suite/api/viewsets.py b/src/backend/plugins/la_suite/api/viewsets.py new file mode 100644 index 0000000..34f05b2 --- /dev/null +++ b/src/backend/plugins/la_suite/api/viewsets.py @@ -0,0 +1,56 @@ +"""API viewsets for La Suite plugin""" + +from functools import reduce +from operator import iconcat + +from rest_framework import viewsets +from rest_framework.mixins import ListModelMixin +from rest_framework.permissions import BasePermission +from rest_framework.response import Response + +from core.authentication.backends import AccountServiceAuthentication +from core.models import Organization + + +class ScopeAPIPermission(BasePermission): + """Permission to check if the user has the correct scope.""" + + def has_permission(self, request, view): + """Check if the user has the correct scope.""" + if not request.auth: + return False + return view.scope in request.user.scopes + + +class ActiveOrganizationsSiret(viewsets.GenericViewSet, ListModelMixin): + """ + ViewSet to list all SIRET of active communes. + + * Requires API key authentication. + * Only services with the correct scope are able to access this view. + """ + + authentication_classes = [AccountServiceAuthentication] + permission_classes = [ScopeAPIPermission] + scope = "la-suite-list-organizations-siret" + + def get_queryset(self): + """ + Return queryset of active communes. + """ + return Organization.objects.filter( + metadata__is_commune=True, + registration_id_list__isnull=False, + is_active=True, + ).order_by("-created_at") + + def list(self, request, *args, **kwargs): + """ + Return a list of all SIRET of active communes. + """ + registration_ids_lists = self.get_queryset().values_list( + "registration_id_list", flat=True + ) + registration_ids = reduce(iconcat, registration_ids_lists, []) + + return Response(registration_ids) diff --git a/src/backend/plugins/la_suite/tests/api/__init__.py b/src/backend/plugins/la_suite/tests/api/__init__.py new file mode 100644 index 0000000..9701443 --- /dev/null +++ b/src/backend/plugins/la_suite/tests/api/__init__.py @@ -0,0 +1 @@ +"""Test for the La Suite plugin API""" diff --git a/src/backend/plugins/la_suite/tests/api/test_active_organizations_siret.py b/src/backend/plugins/la_suite/tests/api/test_active_organizations_siret.py new file mode 100644 index 0000000..90c5afe --- /dev/null +++ b/src/backend/plugins/la_suite/tests/api/test_active_organizations_siret.py @@ -0,0 +1,81 @@ +"""Test for the La Suite plugin API active organizations siret""" + +from importlib import import_module, reload + +from django.test import override_settings +from django.urls import clear_url_caches, set_urlconf + +import pytest +from rest_framework import status +from rest_framework.test import APIClient + +from core import factories + +pytestmark = pytest.mark.django_db + +API_URL = "/la-suite/v1.0/siret/" + + +@pytest.fixture(name="plugin_urls") +def reload_urlconf(settings): + """Reload the urlconf before""" + settings.INSTALLED_PLUGINS = ["plugins.la_suite"] + reload(import_module("core.plugins.urls")) + reload(import_module(settings.ROOT_URLCONF)) + + clear_url_caches() + set_urlconf(None) + + yield + + settings.INSTALLED_PLUGINS = [] + reload(import_module("core.plugins.urls")) + reload(import_module(settings.ROOT_URLCONF)) + + clear_url_caches() + set_urlconf(None) + + +# pylint: disable=unused-argument +def test_active_organizations_siret_unauthorized(plugin_urls): + """Test the active organizations siret API unauthorized""" + client = APIClient() + response = client.get(API_URL) + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +# pylint: disable=unused-argument +@override_settings(ACCOUNT_SERVICE_SCOPES=["la-suite-list-organizations-siret"]) +def test_active_organizations_siret_authorized(plugin_urls): + """Test the active organizations siret API authorized""" + account_service = factories.AccountServiceFactory( + name="my_account_service", + api_key="my_api_key", + scopes=["la-suite-list-organizations-siret"], + ) + factories.OrganizationFactory( + metadata={"is_public_service": True, "is_commune": True}, + registration_id_list=["11111111111111", "22222222222222"], + is_active=True, + ) + factories.OrganizationFactory( + metadata={"is_public_service": True, "is_commune": False}, + registration_id_list=["33333333333333"], + is_active=True, + ) + factories.OrganizationFactory( + metadata={"is_public_service": True, "is_commune": True}, + registration_id_list=["44444444444444"], + is_active=False, + ) + factories.OrganizationFactory( + metadata={"is_public_service": True, "is_commune": False}, + registration_id_list=["55555555555555"], + is_active=True, + ) + client = APIClient() + client.credentials(HTTP_AUTHORIZATION=f"ApiKey {account_service.api_key}") + + response = client.get(API_URL) + assert response.status_code == status.HTTP_200_OK + assert response.data == ["11111111111111", "22222222222222"] diff --git a/src/backend/plugins/la_suite/urls.py b/src/backend/plugins/la_suite/urls.py new file mode 100644 index 0000000..d1fb80f --- /dev/null +++ b/src/backend/plugins/la_suite/urls.py @@ -0,0 +1,14 @@ +"""API URL Configuration for La Suite plugin""" + +from rest_framework.routers import DefaultRouter + +from .api.viewsets import ActiveOrganizationsSiret + +router = DefaultRouter() +router.register( + "la-suite/v1.0/siret", + ActiveOrganizationsSiret, + basename="active-organization-sirets", +) + +urlpatterns = router.urls