From c7f23cbb635f48b65cd23151dbdc3bec61717b72 Mon Sep 17 00:00:00 2001 From: Laurent Bossavit Date: Tue, 3 Dec 2024 16:12:02 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB(keycloak)?= =?UTF-8?q?=20add=20siret=20attribute=20and=20mapper=20to=20Keycloak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can now find organization data as provided by ProConnect in user_info --- docker/auth/realm.json | 60 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/docker/auth/realm.json b/docker/auth/realm.json index 0386314c6..f14d9e837 100644 --- a/docker/auth/realm.json +++ b/docker/auth/realm.json @@ -58,6 +58,23 @@ ], "realmRoles": ["user"] }, + { + "username": "marie", + "email": "marie.varzy@gmail.com", + "firstName": "Marie", + "lastName": "Devarzy", + "enabled": true, + "attributes": { + "siret": "21580304000017" + }, + "credentials": [ + { + "type": "password", + "value": "people" + } + ], + "realmRoles": ["user"] + }, { "username": "user-e2e-chromium", "email": "user@chromium.e2e", @@ -695,9 +712,17 @@ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, "webAuthnPolicyPasswordlessAcceptableAaguids": [], "scopeMappings": [ + { + "clientScope": "siret", + "roles": [ + "user" + ] + }, { "clientScope": "offline_access", - "roles": ["offline_access"] + "roles": [ + "offline_access" + ] } ], "clientScopeMappings": { @@ -947,6 +972,7 @@ "acr", "roles", "profile", + "siret", "email" ], "optionalClientScopes": [ @@ -1107,6 +1133,35 @@ } ] }, + { + "id": "eb220fbb-02ac-4105-95a3-727954f6565d", + "name": "siret", + "description": "siret", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false", + "gui.order": "" + }, + "protocolMappers": [ + { + "id": "333a4e89-9363-4c36-b56f-79c6b019c6c6", + "name": "siret", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "false", + "user.attribute": "siret", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "siret" + } + } + ] + }, { "id": "af52ccc3-4ecb-49b4-9a67-5d4172f16070", "name": "role_list", @@ -1573,7 +1628,8 @@ "email", "roles", "web-origins", - "acr" + "acr", + "siret" ], "defaultOptionalClientScopes": [ "offline_access", From d9c6f63a52c8a70968b14af911ec368254c28ec0 Mon Sep 17 00:00:00 2001 From: Laurent Bossavit Date: Mon, 23 Dec 2024 17:09:43 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=85(e2e)=20change=20accounts=20to=20f?= =?UTF-8?q?acilitate=20SIRET=20and=20add=20e2e=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We also add registration ID info to the /me endpoint, via serializers --- CHANGELOG.md | 4 +++ docker/auth/realm.json | 4 +-- src/backend/core/api/client/serializers.py | 4 +-- .../core/tests/users/test_api_users_list.py | 25 ++++++++++++++++--- .../tests/users/test_api_users_retrieve.py | 1 + src/backend/people/settings.py | 2 ++ src/backend/pyproject.toml | 1 + .../apps/e2e/__tests__/app-desk/siret.spec.ts | 23 +++++++++++++++++ src/helm/env.d/dev/values.desk.yaml.gotmpl | 1 + 9 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/frontend/apps/e2e/__tests__/app-desk/siret.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e18c2283c..e9664bdd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- 🧑‍💻(oidc) add ability to pull registration ID (e.g. SIRET) from OIDC #577 + ### Fixed - 🧑‍💻(user) fix the User.language infinite migration #611 diff --git a/docker/auth/realm.json b/docker/auth/realm.json index f14d9e837..05930cb95 100644 --- a/docker/auth/realm.json +++ b/docker/auth/realm.json @@ -59,7 +59,7 @@ "realmRoles": ["user"] }, { - "username": "marie", + "username": "e2e.marie", "email": "marie.varzy@gmail.com", "firstName": "Marie", "lastName": "Devarzy", @@ -70,7 +70,7 @@ "credentials": [ { "type": "password", - "value": "people" + "value": "password-e2e.marie" } ], "realmRoles": ["user"] diff --git a/src/backend/core/api/client/serializers.py b/src/backend/core/api/client/serializers.py index 9236b7833..df0410193 100644 --- a/src/backend/core/api/client/serializers.py +++ b/src/backend/core/api/client/serializers.py @@ -88,8 +88,8 @@ class UserOrganizationSerializer(serializers.ModelSerializer): class Meta: model = models.Organization - fields = ["id", "name"] - read_only_fields = ["id", "name"] + fields = ["id", "name", "registration_id_list"] + read_only_fields = ["id", "name", "registration_id_list"] class UserSerializer(DynamicFieldsModelSerializer): diff --git a/src/backend/core/tests/users/test_api_users_list.py b/src/backend/core/tests/users/test_api_users_list.py index 8520d7af6..023bae54e 100644 --- a/src/backend/core/tests/users/test_api_users_list.py +++ b/src/backend/core/tests/users/test_api_users_list.py @@ -4,6 +4,7 @@ from unittest import mock +import jq import pytest from rest_framework.status import ( HTTP_200_OK, @@ -77,7 +78,13 @@ def test_api_users_list_authenticated_response_content( response = client.get("/api/v1.0/users/") assert response.status_code == HTTP_200_OK - assert response.json() == { + json = response.json() + edited_json = ( + jq.compile(".results[] |= (.organization |= del(.registration_id_list))") + .input(json) + .first() + ) + assert edited_json == { "count": 2, "next": None, "previous": None, @@ -155,7 +162,13 @@ def test_api_users_authenticated_list_by_email(): response = client.get("/api/v1.0/users/?q=ool") assert response.status_code == HTTP_200_OK - assert response.json()["results"] == [ + json = response.json() + edited_json = ( + jq.compile(".results[] |= (.organization |= del(.registration_id_list))") + .input(json) + .first() + ) + assert edited_json["results"] == [ { "id": str(frank.id), "email": frank.email, @@ -228,7 +241,13 @@ def test_api_users_authenticated_list_by_name(): response = client.get("/api/v1.0/users/?q=oole") assert response.status_code == HTTP_200_OK - assert response.json()["results"] == [ + json = response.json() + edited_json = ( + jq.compile(".results[] |= (.organization |= del(.registration_id_list))") + .input(json) + .first() + ) + assert edited_json["results"] == [ { "id": str(frank.id), "email": frank.email, diff --git a/src/backend/core/tests/users/test_api_users_retrieve.py b/src/backend/core/tests/users/test_api_users_retrieve.py index 61621bee9..9bb217b14 100644 --- a/src/backend/core/tests/users/test_api_users_retrieve.py +++ b/src/backend/core/tests/users/test_api_users_retrieve.py @@ -61,6 +61,7 @@ def test_api_users_retrieve_me_authenticated(): "organization": { "id": str(user.organization.pk), "name": user.organization.name, + "registration_id_list": user.organization.registration_id_list, }, } diff --git a/src/backend/people/settings.py b/src/backend/people/settings.py index 6c1bb41bd..f1f5e7a34 100755 --- a/src/backend/people/settings.py +++ b/src/backend/people/settings.py @@ -639,6 +639,8 @@ class Development(Base): # this is a dev credentials for mail provisioning API MAIL_PROVISIONING_API_CREDENTIALS = "bGFfcmVnaWU6cGFzc3dvcmQ=" + OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret" + def __init__(self): """In dev, force installs needed for Swagger API.""" # pylint: disable=invalid-name diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 9405a45e4..576262b61 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -66,6 +66,7 @@ dev = [ "drf-spectacular-sidecar==2024.12.1", "ipdb==0.13.13", "ipython==8.31.0", + "jq==1.8.0", "pyfakefs==5.7.3", "pylint-django==2.6.1", "pylint==3.3.2", diff --git a/src/frontend/apps/e2e/__tests__/app-desk/siret.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/siret.spec.ts new file mode 100644 index 000000000..b6b78779c --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-desk/siret.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; + +import { keyCloakSignIn } from './common'; + +test.beforeEach(async ({ page, browserName }) => { + await page.goto('/'); + await keyCloakSignIn(page, browserName, 'marie'); +}); + +test.describe('OIDC interop with SIRET', () => { + 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( + 'http://localhost:8071/api/v1.0/users/me/', + ); + expect(response.ok()).toBeTruthy(); + expect(await response.json()).toMatchObject({ + organization: { registration_id_list: ['21580304000017'] }, + }); + }); +}); diff --git a/src/helm/env.d/dev/values.desk.yaml.gotmpl b/src/helm/env.d/dev/values.desk.yaml.gotmpl index fc5354233..eaac1d125 100644 --- a/src/helm/env.d/dev/values.desk.yaml.gotmpl +++ b/src/helm/env.d/dev/values.desk.yaml.gotmpl @@ -26,6 +26,7 @@ backend: OIDC_OP_TOKEN_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/token OIDC_OP_USER_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/userinfo OIDC_OP_LOGOUT_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/session/end + OIDC_ORGANIZATION_REGISTRATION_ID_FIELD: "siret" OIDC_RP_CLIENT_ID: secretKeyRef: name: backend