✨(anct) fetch and display organization names of communes
ANCT-specific extraction of organization names for communes, front end changes to match.
This commit is contained in:
committed by
Laurent Bossavit
parent
d495ef3e19
commit
20cc173e93
@@ -10,6 +10,7 @@ and this project adheres to
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- ✨(anct) fetch and display organization names of communes #583
|
||||||
- ✨(frontend) display email if no username #562
|
- ✨(frontend) display email if no username #562
|
||||||
- 🧑💻(oidc) add ability to pull registration ID (e.g. SIRET) from OIDC #577
|
- 🧑💻(oidc) add ability to pull registration ID (e.g. SIRET) from OIDC #577
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
"username": "e2e.marie",
|
"username": "e2e.marie",
|
||||||
"email": "marie.varzy@gmail.com",
|
"email": "marie.varzy@gmail.com",
|
||||||
"firstName": "Marie",
|
"firstName": "Marie",
|
||||||
"lastName": "Devarzy",
|
"lastName": "Delamairie",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"siret": "21580304000017"
|
"siret": "21580304000017"
|
||||||
|
|||||||
@@ -641,6 +641,8 @@ class Development(Base):
|
|||||||
|
|
||||||
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret"
|
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret"
|
||||||
|
|
||||||
|
ORGANIZATION_PLUGINS = ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""In dev, force installs needed for Swagger API."""
|
"""In dev, force installs needed for Swagger API."""
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@@ -686,6 +688,10 @@ class Test(Base):
|
|||||||
# this is a dev credentials for mail provisioning API
|
# this is a dev credentials for mail provisioning API
|
||||||
MAIL_PROVISIONING_API_CREDENTIALS = "bGFfcmVnaWU6cGFzc3dvcmQ="
|
MAIL_PROVISIONING_API_CREDENTIALS = "bGFfcmVnaWU6cGFzc3dvcmQ="
|
||||||
|
|
||||||
|
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret"
|
||||||
|
|
||||||
|
ORGANIZATION_PLUGINS = ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||||
|
|
||||||
ORGANIZATION_REGISTRATION_ID_VALIDATORS = [
|
ORGANIZATION_REGISTRATION_ID_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.core.validators.RegexValidator",
|
"NAME": "django.core.validators.RegexValidator",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from requests.adapters import HTTPAdapter, Retry
|
||||||
|
|
||||||
from core.plugins.base import BaseOrganizationPlugin
|
from core.plugins.base import BaseOrganizationPlugin
|
||||||
|
|
||||||
@@ -33,20 +34,17 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
|||||||
logger.warning("Empty list 'liste_enseignes' in %s", organization_data)
|
logger.warning("Empty list 'liste_enseignes' in %s", organization_data)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_organization_name_from_siret(self, siret):
|
def get_organization_name_from_results(self, data, siret):
|
||||||
"""Return the organization name from the SIRET."""
|
"""Return the organization name from the results of a SIRET search."""
|
||||||
try:
|
|
||||||
response = requests.get(self._api_url.format(siret=siret), timeout=10)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
except requests.RequestException as exc:
|
|
||||||
logger.exception("%s: Unable to fetch organization name from SIRET", exc)
|
|
||||||
return None
|
|
||||||
|
|
||||||
for result in data["results"]:
|
for result in data["results"]:
|
||||||
|
nature = "nature_juridique"
|
||||||
|
commune = nature in result and result[nature] == "7210"
|
||||||
for organization in result["matching_etablissements"]:
|
for organization in result["matching_etablissements"]:
|
||||||
if organization.get("siret") == siret:
|
if organization.get("siret") == siret:
|
||||||
return self._extract_name_from_organization_data(organization)
|
if commune:
|
||||||
|
return organization["libelle_commune"].title()
|
||||||
|
|
||||||
|
return self._extract_name_from_organization_data(organization)
|
||||||
|
|
||||||
logger.warning("No organization name found for SIRET %s", siret)
|
logger.warning("No organization name found for SIRET %s", siret)
|
||||||
return None
|
return None
|
||||||
@@ -63,10 +61,21 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
|||||||
|
|
||||||
# In the nominal case, there is only one registration ID because
|
# In the nominal case, there is only one registration ID because
|
||||||
# the organization as been created from it.
|
# the organization as been created from it.
|
||||||
name = self._get_organization_name_from_siret(
|
try:
|
||||||
organization.registration_id_list[0]
|
# Retry logic as the API may be rate limited
|
||||||
)
|
s = requests.Session()
|
||||||
if not name:
|
retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[429])
|
||||||
|
s.mount("https://", HTTPAdapter(max_retries=retries))
|
||||||
|
|
||||||
|
siret = organization.registration_id_list[0]
|
||||||
|
response = s.get(self._api_url.format(siret=siret), timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
name = self.get_organization_name_from_results(data, siret)
|
||||||
|
if not name:
|
||||||
|
return
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
logger.exception("%s: Unable to fetch organization name from SIRET", exc)
|
||||||
return
|
return
|
||||||
|
|
||||||
organization.name = name
|
organization.name = name
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import responses
|
|||||||
from core.models import Organization
|
from core.models import Organization
|
||||||
from core.plugins.loader import get_organization_plugins
|
from core.plugins.loader import get_organization_plugins
|
||||||
|
|
||||||
|
from plugins.organizations import NameFromSiretOrganizationPlugin
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
@@ -135,3 +137,34 @@ def test_organization_plugins_run_after_create_name_already_set(
|
|||||||
name="Magic WOW", registration_id_list=["12345678901234"]
|
name="Magic WOW", registration_id_list=["12345678901234"]
|
||||||
)
|
)
|
||||||
assert organization.name == "Magic WOW"
|
assert organization.name == "Magic WOW"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_name_from_org_data_when_commune(
|
||||||
|
organization_plugins_settings,
|
||||||
|
):
|
||||||
|
"""Test the name is extracted correctly for a French commune."""
|
||||||
|
data = {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"nom_complet": "COMMUNE DE VARZY",
|
||||||
|
"nom_raison_sociale": "COMMUNE DE VARZY",
|
||||||
|
"siege": {
|
||||||
|
"libelle_commune": "VARZY",
|
||||||
|
"liste_enseignes": ["MAIRIE"],
|
||||||
|
"siret": "21580304000017",
|
||||||
|
},
|
||||||
|
"nature_juridique": "7210",
|
||||||
|
"matching_etablissements": [
|
||||||
|
{
|
||||||
|
"siret": "21580304000017",
|
||||||
|
"libelle_commune": "VARZY",
|
||||||
|
"liste_enseignes": ["MAIRIE"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin = NameFromSiretOrganizationPlugin()
|
||||||
|
name = plugin.get_organization_name_from_results(data, "21580304000017")
|
||||||
|
assert name == "Varzy"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface User {
|
|||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
organization?: Organization;
|
||||||
abilities?: {
|
abilities?: {
|
||||||
mailboxes: UserAbilities;
|
mailboxes: UserAbilities;
|
||||||
contacts: UserAbilities;
|
contacts: UserAbilities;
|
||||||
@@ -16,6 +17,12 @@ export interface User {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Organization {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
registration_id_list: [string];
|
||||||
|
}
|
||||||
|
|
||||||
export type UserAbilities = {
|
export type UserAbilities = {
|
||||||
can_view?: boolean;
|
can_view?: boolean;
|
||||||
can_create?: boolean;
|
can_create?: boolean;
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ export const AccountDropdown = () => {
|
|||||||
<DropButton
|
<DropButton
|
||||||
button={
|
button={
|
||||||
<Box $flex $direction="row" $align="center">
|
<Box $flex $direction="row" $align="center">
|
||||||
<Text $theme="primary">{userName}</Text>
|
<Box $flex $direction="column" $align="left">
|
||||||
|
<Text $theme="primary">{userName}</Text>
|
||||||
|
{userData?.organization?.registration_id_list?.at(0) && (
|
||||||
|
<Text $theme="primary">{userData?.organization?.name}</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<Text className="material-icons" $theme="primary" aria-hidden="true">
|
<Text className="material-icons" $theme="primary" aria-hidden="true">
|
||||||
arrow_drop_down
|
arrow_drop_down
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ test.beforeEach(async ({ page, browserName }) => {
|
|||||||
|
|
||||||
test.describe('OIDC interop with SIRET', () => {
|
test.describe('OIDC interop with SIRET', () => {
|
||||||
test('it checks the SIRET is displayed in /me endpoint', async ({ page }) => {
|
test('it checks the SIRET is displayed in /me endpoint', async ({ page }) => {
|
||||||
const header = page.locator('header').first();
|
|
||||||
await expect(header.getByAltText('Marianne Logo')).toBeVisible();
|
|
||||||
|
|
||||||
const response = await page.request.get(
|
const response = await page.request.get(
|
||||||
'http://localhost:8071/api/v1.0/users/me/',
|
'http://localhost:8071/api/v1.0/users/me/',
|
||||||
);
|
);
|
||||||
@@ -21,3 +18,16 @@ test.describe('OIDC interop with SIRET', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('When a commune, display commune name below user name', () => {
|
||||||
|
test('it checks the name is added below the user name', async ({ page }) => {
|
||||||
|
const header = page.locator('header').first();
|
||||||
|
await expect(header.getByAltText('Marianne Logo')).toBeVisible();
|
||||||
|
|
||||||
|
const logout = page.getByRole('button', {
|
||||||
|
name: 'Marie Delamairie',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(logout.getByText('Varzy')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ backend:
|
|||||||
OIDC_RP_SCOPES: "openid email siret"
|
OIDC_RP_SCOPES: "openid email siret"
|
||||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://desk-preprod.beta.numerique.gouv.fr
|
OIDC_REDIRECT_ALLOWED_HOSTS: https://desk-preprod.beta.numerique.gouv.fr
|
||||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
||||||
ORGANIZATION_PLUGINS: "plugins.organizations.NameFromSiretOrganizationPlugin"
|
ORGANIZATION_PLUGINS: ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||||
ORGANIZATION_REGISTRATION_ID_VALIDATORS: '[{"NAME": "django.core.validators.RegexValidator", "OPTIONS": {"regex": "^[0-9]{14}$"}}]'
|
ORGANIZATION_REGISTRATION_ID_VALIDATORS: '[{"NAME": "django.core.validators.RegexValidator", "OPTIONS": {"regex": "^[0-9]{14}$"}}]'
|
||||||
LOGIN_REDIRECT_URL: https://desk-preprod.beta.numerique.gouv.fr
|
LOGIN_REDIRECT_URL: https://desk-preprod.beta.numerique.gouv.fr
|
||||||
LOGIN_REDIRECT_URL_FAILURE: https://desk-preprod.beta.numerique.gouv.fr
|
LOGIN_REDIRECT_URL_FAILURE: https://desk-preprod.beta.numerique.gouv.fr
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ backend:
|
|||||||
OIDC_OP_TOKEN_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/token
|
OIDC_OP_TOKEN_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/token
|
||||||
OIDC_OP_USER_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/userinfo
|
OIDC_OP_USER_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/userinfo
|
||||||
OIDC_OP_LOGOUT_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/session/end
|
OIDC_OP_LOGOUT_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/session/end
|
||||||
ORGANIZATION_PLUGINS: "plugins.organizations.NameFromSiretOrganizationPlugin"
|
ORGANIZATION_PLUGINS: ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||||
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD: "siret"
|
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD: "siret"
|
||||||
OIDC_RP_CLIENT_ID:
|
OIDC_RP_CLIENT_ID:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
Reference in New Issue
Block a user