Connexion: Ajout de ProConnect pour les Prescripteurs et Employeurs#4080
Conversation
|
🥁 La recette jetable est prête ! 👉 Je veux tester cette PR ! |
ab0a277 to
e6bb867
Compare
822f01f to
14ba96f
Compare
461731a to
d0d1396
Compare
b9d84a5 to
559aef0
Compare
c5bffd3 to
6053bfb
Compare
rsebille
left a comment
There was a problem hiding this comment.
Revue partielle mais là j'ai plus de jus.
Remarques globales :
- Pas super fan d'avoir des choses nommées IC et de faire IC+PC dedans car ça ne permet pas d'identifier simplement ce qui est spécifique ou partagé
- Pratiquement pas relu les tests, un peu trop de bruit et le jeu des 7 différences est trop casse-pied 😁
- Je me demande plutôt qu'avoir les deux en // si ça n'aurais pas été plus simple d'avoir un switch global qui indique le SSO par défaut et de gérer ça avec les variables existantes ou des templatetags/filter si on veux pas trop se faire chier. Ça aurais permis de lancer tout les tests avec l'un ou l'autre jusqu'au jour J, et la majorité du taf aurais été dans le renommage/refacto préalable plutôt que de dupliquer du code et de créer des branches de code qu'il faudra par la suite nettoyer.
There was a problem hiding this comment.
Pas super convaincu de l'indirection causée par la recopie des settings dans une constante.
There was a problem hiding this comment.
C'est comme ça avec tous les SSO, et j'avoue que je trouve ça plus simple de laisser en place.
C'était parti d'une logique de victor de ne pas mettre dans les settings des trucs qui ne l'étaient pas, et ça reste dicsutable.
On pourrait (dans une autre PR) décider de tout remettre dans les settings, quitte à avoir une sorte de meta setting PRO_CONNECT qui contienne des attributs calculés ou juste configurés pour tout regrouper proprement.
Un peu comme pour la logique des settings des libs blue à l'époque.
There was a problem hiding this comment.
Je suis plutôt team "séparer les constantes du code des paramètres de configuration" ;).
Et pour préciser mon commentaire, dans le fichier actuel ce n'est pas les URL qui me dérange, elles ont leur place ici plutôt que dans les settings vu qu'elle n'"active" rien ni ne sont des secrets ou configurable.
Par contre le schéma VAR = settings.VAR n'a strictement aucun intérêt, et je suppose que tout ça sert pour le reload_module() parce qu'on importe les symboles directement plutôt que les modules donc plus compliqué de mocké.
There was a problem hiding this comment.
Euh, c'est possible 🤔
Je ne me rappelle plus pourquoi on avait fait comme ça.
Il me semble que ce n'est pas nécessaire parce que le reload_module est de toute façon lancé pour que le override_settings fasse effet.
Comme en plus les endpoints dépendent des settings, ça rend la frontière un peu floue et je crois qu'au final c'était une manière de tout avoir au même endroit.
| # https://github.com/numerique-gouv/agentconnect-documentation/blob/main/doc_fs/donnees_fournies.md | ||
| PRO_CONNECT_SCOPES = "openid email given_name usual_name" |
There was a problem hiding this comment.
Les scopes email given_name usual_name sont vraiment nécessaires et utiles ? Ou ça tombe en marche ? Ou alors c'est la doc qui n'est pas claire ?
Car de ce que je comprend de la doc d'PC ces informations sont obligatoirement renvoyées, et elles ne sont pas listées dans la doc OpenID pointée 😵.
There was a problem hiding this comment.
Normalement il ne sont plus censé être nécessaires, mais sans, ça plante.... 🤷
j'ajoute un commentaire.
There was a problem hiding this comment.
C'était parce que les scopes n'étaient pas cochés coté ProConnect.
normalement maintenant c'est bon.
| def asdict(self): | ||
| return dataclasses.asdict(self) |
There was a problem hiding this comment.
Haha, on se retrouve à devoir faire plus de code pour adhérer à la convention que d'utiliser le helper directement 😀. Même .bind_to_request()ne l'utilise pas 😅.
There was a problem hiding this comment.
C'est utilisé dans OIDConnectUserData.create_or_update_user
There was a problem hiding this comment.
Si tu parles d'ici alors ce n'est pas utilisé :).
https://github.com/gip-inclusion/les-emplois/blob/e69ba85ea3631a1d38d5c55083d64e6e6bdd324e/itou/openid_connect/models.py#L116C38-L116C44
A priori les seules utilisations sont celles-ci :
❯ rg -tpy '\.asdict\(\)'
tests/openid_connect/pro_connect/tests.py
678: pc_session_dict = pc_session.asdict()
itou/openid_connect/inclusion_connect/views.py
141: data = _generate_inclusion_params_from_session(ic_data.asdict())
tests/openid_connect/inclusion_connect/tests.py
722: ic_session_dict = ic_session.asdict()
itou/openid_connect/pro_connect/views.py
136: data = _generate_pro_params_from_session(pc_data.asdict())Alors que l'utilisation direct du module (rg -tpy 'dataclasses\.asdict\(') en fait ressentir le quadruple, dont 4 occurrences sont en fait pour la convention/interface.
Donc je dirais que c'est assez peu utilisé et qu'en plus ça oblige à maintenir une interface dans chaque classe enfant pour un un gain nul.
There was a problem hiding this comment.
Ah, mince j'ai confondu d'endroit.
C'est utilisé ligne 136
Mais en effet, on se retrouve à définir une fonction qui n'est utilisée qu'à un endroit, c'est pas super utile. Je vais remplacer.
| @respx.mock | ||
| @override_pro_connect_settings | ||
| def test_prescriber_using_django_has_to_activate_pro_connect_account(client): | ||
| user = PrescriberFactory(identity_provider=IdentityProvider.DJANGO, email=OIDC_USERINFO["email"]) | ||
| client.force_login(user) | ||
| url = reverse("dashboard:index") | ||
| response = client.get(url, follow=True) | ||
| activate_ic_account_url = reverse("dashboard:activate_ic_account") | ||
| assertRedirects(response, activate_ic_account_url) | ||
| params = { | ||
| "user_kind": UserKind.PRESCRIBER, | ||
| "previous_url": activate_ic_account_url, | ||
| "user_email": user.email, | ||
| } | ||
| url = escape(f"{reverse('pro_connect:authorize')}?{urlencode(params)}") | ||
| assertContains(response, url + '"') | ||
| response = mock_oauth_dance( | ||
| client, | ||
| UserKind.PRESCRIBER, | ||
| previous_url=activate_ic_account_url, | ||
| ) | ||
| user.refresh_from_db() | ||
| assert user.identity_provider == IdentityProvider.PRO_CONNECT | ||
|
|
||
|
|
||
| @respx.mock | ||
| @override_pro_connect_settings | ||
| def test_employer_using_django_has_to_activate_pro_connect_account(client): | ||
| user = EmployerFactory(with_company=True, identity_provider=IdentityProvider.DJANGO, email=OIDC_USERINFO["email"]) | ||
| client.force_login(user) | ||
| url = reverse("dashboard:index") | ||
| response = client.get(url, follow=True) | ||
| activate_ic_account_url = reverse("dashboard:activate_ic_account") | ||
| assertRedirects(response, activate_ic_account_url) | ||
| params = { | ||
| "user_kind": UserKind.EMPLOYER, | ||
| "previous_url": activate_ic_account_url, | ||
| "user_email": user.email, | ||
| } | ||
| url = escape(f"{reverse('pro_connect:authorize')}?{urlencode(params)}") | ||
| assertContains(response, url + '"') | ||
| response = mock_oauth_dance( | ||
| client, | ||
| UserKind.EMPLOYER, | ||
| previous_url=activate_ic_account_url, | ||
| ) | ||
| user.refresh_from_db() | ||
| assert user.identity_provider == IdentityProvider.PRO_CONNECT | ||
|
|
||
|
|
There was a problem hiding this comment.
Ces 2 tests ne devraient-il pas être dans leur propre fichier plutôt que celui d'IC ? En plus ça éviterais les renommages.
Et vu de la tête du fichier et que PC est globalement un drop-in de IC, je pense qu'une fixture paramétrique permettrais de tester les 2 fournisseurs sans avoir à dupliquer tout les tests.
There was a problem hiding this comment.
Je dois avouer que j'ai fait au plus simple pour la suppression après coup.
J'aurais pu faire 2 vues différents, utiliser un switch encore plus élégant, un paramétrise des tests, etc, sauf que ça nécessite encore plus de travail : les InclusionConnectTestCase et ProConnectTestCase ne sont pas compatibles entre eux donc il faudrait les appliquer de manière paramétrique également sur les tests.
There was a problem hiding this comment.
Pour les TestCase je suis d'accord que c'est pas le plus pratique, pour ça que j'ai pas fait la remarque ;).
Mais vu que ce fichier est en full pytest, que justement c'est les mêmes vues, et qu'on duplique des tests (mais pas tous) ça m'a fait penser à une fixture paramétrique histoire de tester les deux chemins de la même manière, mais si t'es serein alors y a pas de problème :).
There was a problem hiding this comment.
J'ai mis dans 2 fichiers séparés pour pouvoir supprimer directement celui d'ic à la fin et comparer les tests facilement avec un vim -d en attendant :)
|
Globalement je suis d'accord avec toi : il y aurait moyen de faire un truc super élégant qui permette de passer à la volée d'un SSO à l'autre dans les canaux de connexion, mais je pense que ça prendrait une grosse semaine de travail pour pas grand chose. Résultat, c'est un peu un jeu des 7 différences avec tous ces tests dupliqués mais je n'ai pas réussi à faire mieux facilement, et je pense que ça ne vaut pas le coup d'y passer trop de temps. |
31d734d to
9909ebb
Compare
|
J’ai commencé par relire « inclusion_connect: Remove unused channel ». C’est possible de le sortir dans une autre PR pour éviter de polluer la discussion sur la migration à PC ? Je pense qu’un peu de contexte dans le commit (pourquoi ça avait été ajouté/pourquoi ce n’est pas utilisé) serait très utile. |
9909ebb to
6186311
Compare
|
C'est fait, j'ai sorti dans une PR séparée |
francoisfreitag
left a comment
There was a problem hiding this comment.
Super, très pratique d’avoir dupliqué les classes de tests juste en dessous, on voit bien ce qui change pour PC.
C’est chouette que ProConnect accepte le hint pour utiliser NEPTUNE.
J’ai vu un changement mineur, qui auraient été mieux à part (changer le niveau de logs de info vers error pour le message "Organization with SAFIR {safir} does not exist. Unable to add user {user.email}."). Là, il est juste perdu dans le flot.
Il reste quelques FIXME pour quand on aura les URLs, je présume.
6186311 to
7b2b414
Compare
|
Merci @rsebille et @francoisfreitag pour la relecture, j'ai pris en compte vos remarques (ou ajouté un commentaire en réponse) dans 2 commits de fixups séparé (un par personne) |
7b2b414 to
e69ba85
Compare
There was a problem hiding this comment.
Je suis plutôt team "séparer les constantes du code des paramètres de configuration" ;).
Et pour préciser mon commentaire, dans le fichier actuel ce n'est pas les URL qui me dérange, elles ont leur place ici plutôt que dans les settings vu qu'elle n'"active" rien ni ne sont des secrets ou configurable.
Par contre le schéma VAR = settings.VAR n'a strictement aucun intérêt, et je suppose que tout ça sert pour le reload_module() parce qu'on importe les symboles directement plutôt que les modules donc plus compliqué de mocké.
| identity_provider: IdentityProvider | ||
| kind: str | ||
| login_allowed_user_kinds = [UserKind] | ||
| login_allowed_user_kinds: ClassVar[list[UserKind]] |
There was a problem hiding this comment.
Si login_allowed_user_kinds est une ClassVar alors identity_provider et kind le sont très probablement aussi ;).
There was a problem hiding this comment.
C'est la seule façon que j'ai trouvé pour réussi à filer list[UserKind] :
Avec login_allowed_user_kinds: list[UserKind] = [UserKind.PRESCRIBER, UserKind.EMPLOYER]
Ça done on obtient une ValueError: mutable default <class 'list'> for field login_allowed_user_kinds is not allowed: use default_factory
There was a problem hiding this comment.
Oui c'est ce que j'ai vu, au début je me suis dit qu'on avais pas spécifiquement besoin d'une list et qu'on pourrais avoir une structure immutable mais le ClassVar est tout aussi vrai vu qu'on veux la même valeur pour toutes les instances de classe, ce qui m'a fait penser que les 2 autres devraient aussi en être.
En soi c'est pas bloquant mais c'est plus pour éviter les Oo quand on arrive dessus et que un seul l'est alors que 3 sont utilisés de la même manière.
There was a problem hiding this comment.
Ah, je comprends mieux.
On ne peut pas le mettre sur les 2 autres parce que sinon, on ne les récupère pas dans user_data_dict = dataclasses.asdict(self) et on il manque des champs pour créer l'utilisateur
There was a problem hiding this comment.
Pour info : j'ai extrait cette partie pour la mettre dans cette PR : #4822
| def asdict(self): | ||
| return dataclasses.asdict(self) |
There was a problem hiding this comment.
Si tu parles d'ici alors ce n'est pas utilisé :).
https://github.com/gip-inclusion/les-emplois/blob/e69ba85ea3631a1d38d5c55083d64e6e6bdd324e/itou/openid_connect/models.py#L116C38-L116C44
A priori les seules utilisations sont celles-ci :
❯ rg -tpy '\.asdict\(\)'
tests/openid_connect/pro_connect/tests.py
678: pc_session_dict = pc_session.asdict()
itou/openid_connect/inclusion_connect/views.py
141: data = _generate_inclusion_params_from_session(ic_data.asdict())
tests/openid_connect/inclusion_connect/tests.py
722: ic_session_dict = ic_session.asdict()
itou/openid_connect/pro_connect/views.py
136: data = _generate_pro_params_from_session(pc_data.asdict())Alors que l'utilisation direct du module (rg -tpy 'dataclasses\.asdict\(') en fait ressentir le quadruple, dont 4 occurrences sont en fait pour la convention/interface.
Donc je dirais que c'est assez peu utilisé et qu'en plus ça oblige à maintenir une interface dans chaque classe enfant pour un un gain nul.
| @respx.mock | ||
| @override_pro_connect_settings | ||
| def test_prescriber_using_django_has_to_activate_pro_connect_account(client): | ||
| user = PrescriberFactory(identity_provider=IdentityProvider.DJANGO, email=OIDC_USERINFO["email"]) | ||
| client.force_login(user) | ||
| url = reverse("dashboard:index") | ||
| response = client.get(url, follow=True) | ||
| activate_ic_account_url = reverse("dashboard:activate_ic_account") | ||
| assertRedirects(response, activate_ic_account_url) | ||
| params = { | ||
| "user_kind": UserKind.PRESCRIBER, | ||
| "previous_url": activate_ic_account_url, | ||
| "user_email": user.email, | ||
| } | ||
| url = escape(f"{reverse('pro_connect:authorize')}?{urlencode(params)}") | ||
| assertContains(response, url + '"') | ||
| response = mock_oauth_dance( | ||
| client, | ||
| UserKind.PRESCRIBER, | ||
| previous_url=activate_ic_account_url, | ||
| ) | ||
| user.refresh_from_db() | ||
| assert user.identity_provider == IdentityProvider.PRO_CONNECT | ||
|
|
||
|
|
||
| @respx.mock | ||
| @override_pro_connect_settings | ||
| def test_employer_using_django_has_to_activate_pro_connect_account(client): | ||
| user = EmployerFactory(with_company=True, identity_provider=IdentityProvider.DJANGO, email=OIDC_USERINFO["email"]) | ||
| client.force_login(user) | ||
| url = reverse("dashboard:index") | ||
| response = client.get(url, follow=True) | ||
| activate_ic_account_url = reverse("dashboard:activate_ic_account") | ||
| assertRedirects(response, activate_ic_account_url) | ||
| params = { | ||
| "user_kind": UserKind.EMPLOYER, | ||
| "previous_url": activate_ic_account_url, | ||
| "user_email": user.email, | ||
| } | ||
| url = escape(f"{reverse('pro_connect:authorize')}?{urlencode(params)}") | ||
| assertContains(response, url + '"') | ||
| response = mock_oauth_dance( | ||
| client, | ||
| UserKind.EMPLOYER, | ||
| previous_url=activate_ic_account_url, | ||
| ) | ||
| user.refresh_from_db() | ||
| assert user.identity_provider == IdentityProvider.PRO_CONNECT | ||
|
|
||
|
|
There was a problem hiding this comment.
Pour les TestCase je suis d'accord que c'est pas le plus pratique, pour ça que j'ai pas fait la remarque ;).
Mais vu que ce fichier est en full pytest, que justement c'est les mêmes vues, et qu'on duplique des tests (mais pas tous) ça m'a fait penser à une fixture paramétrique histoire de tester les deux chemins de la même manière, mais si t'es serein alors y a pas de problème :).
e69ba85 to
55fd4c1
Compare
| identity_provider: IdentityProvider | ||
| kind: str | ||
| login_allowed_user_kinds = [UserKind] | ||
| login_allowed_user_kinds: ClassVar[list[UserKind]] |
There was a problem hiding this comment.
Oui c'est ce que j'ai vu, au début je me suis dit qu'on avais pas spécifiquement besoin d'une list et qu'on pourrais avoir une structure immutable mais le ClassVar est tout aussi vrai vu qu'on veux la même valeur pour toutes les instances de classe, ce qui m'a fait penser que les 2 autres devraient aussi en être.
En soi c'est pas bloquant mais c'est plus pour éviter les Oo quand on arrive dessus et que un seul l'est alors que 3 sont utilisés de la même manière.
0a95fe5 to
016dbb1
Compare
|
🥁 La recette jetable est prête ! 👉 Je veux tester cette PR ! |
🤔 Pourquoi ?
Inclusion Connect va être remplacé par ProConnect au
1er octobre 2024(un jour peut être)la bascule se fera en renseignant le setting
PRO_CONNECT_BASE_URLIl reste quelques fixme (des liens de doc/aide à ajouter au niveau des boutons de connexion ProConnect) mais tout le reste est normalement bon.
🏝️ Comment tester
Aujourd'hui on n'a qu'un compte ProConnect de test :
le compte ProConnet est
user@yopmail.com(mot de passe aussiuser@yopmail.com)les parcours sont
user@yopmail.comavant via l'admin)DJANGOet l'emailuser@yopmail.com