ZEP-17 : Elaboration de l'API des membres

a marqué ce sujet comme résolu.

@Javier : Oui mais si je parle des tests c'est autant et surtout pour detecter les regression. Tu remarquera que dans le suite je propose qu'on informe les dev quand il y aura des choses de cassés. Je suis loin d'etre contre casser la rétro compatibilité. Je dis juste que c'est pas l'urgence de l'assurer, d'autant que ce sera supporté presque automatiquement dans quelques mois.

Bref pour moi, on continue en indiquant un gros "beta" sur ce service et quand DRF supportera le verisonnement, on pourra en reparler et sortir de la beta avec un versionnement.

@Firm1 : La je suis d'accord avec toi, il faut étoffer cette doc pour qu'elle fasse plus que balancer des urls, même testables.

C'est une première itération, on n'a pas passé énormément de temps sur la doc et faudra explorer un peu swagger pour voir les possibilités, mais je suis quasi sûre qu'il est possible par exemple d'avoir :

  • les entêtes qu'on peut envoyer
  • les types de données et leurs limitations

Il faut juste l'ajouter de la bonne manière pour que ça soit pris en compte par swagger (c'est en tout cas le cas avec swagger java, je suppose qu'avec django aussi)

Quant à la procédure de demande d'accès + login, c'est prévu aussi, mais c'est hors scope de cette ZEP ci.

A mon avis, ce sera soit développé dans une ZEP parallèle, soit ça fera l'objet d'une ZEP finale "Nettoyage/finalisation" qui concluera la méga-ZEP-2.

+0 -0

J'ai une question, encore, concernant l'authentification : si je fais un client, je dois inclure pour qu'il communique avec l'API, un id client et le secret associé. Que le projet soit open-source ou pas, il est toujours facile de récupérer les chaines. Donc n'importe qui pourra piquer mon identifiant et secret pour ce faire passer pour mon application, non ? Dès lors, a quoi servent ils tellement c'est simple a piquer celui de quelqu'un d'autre.

Malheureusement, à ma connaissance, il n'existe aucune solution pour ce problème. N'importe qui peut utiliser des clients de développeurs et s'authentifier via ce client (l'authentification reste sécurisé) mais je ne vois pas bien ce que nous pourrions y faire. Par exemple, téléchargez l'apk de n'importe quel client Twitter, vous pourrez retrouver les identifiants du client pour pouvoir vous identifiez (avec un peu de recherche).

Ok, soit.

J'ai encore une dernière question qui est plus général que cette API : Quel est la différence entre l'access token et le refresh token ?

Au passage il y a un champs "expires_in", c'est la date (au format timestamp utc) d'expiration ?

Me semble que dans OAuth il existe un "flow" qui n'utilise pas de CLIENT_SECRET. Justement pour le cas des apps mobiles ou des navigateurs qui ne peuvent pas "cacher" leur CLIENT_SECRET.

Je l'ai rêvé, ou… ?

@Kje : regarde la spec OAuth. Le token a une durée de vie relativement courte, quand il expire tu peux te servir du refresh token pour en obtenir un nouveau sans forcer l'utilisateur à passer par la case "retape tes credentials". Un refresh_token consommé n'est plus valide.

+1 -0

Désolé, je vais faire le flemmard mais l'authentification a été expliqué très largement pendant le sujet et résumé sur le premier message de ce sujet :

v0.4 : Gestion de l'authentification.
Objectif

Permettre une authentification OAuth2 (dont la spécification du protocole est disponible via ce lien).

Son fonctionnement est le suivant :

  • Chaque client de l'API s'inscrit en tant qu'application tierce : ça signifie qu'il faut prévoir une table de stockage des clients de l'API
  • A la fin de l'inscription, le système devra renvoyer une ZDS_AUTH_KEY et une ZDS_AUTH_SECRET au développeur
  • Lorsqu'un membre veut se logguer au site via un client externe, le client devra donc envoyer à l'API:
    • le login : login de l'utilisateur sur le site
    • le password : mot de passe de l'utilisateur sur le site
    • le ZDS_AUTH_KEY : identifiant du client de l'API
    • le ZDS_AUTH_SECRET : clé secrète du client de l'API
  • Un token doit être généré qui signifie "le membre x veut se connecter à l'API via le client y". Ce token doit avoir une date d'expiration (à définir) et devra être utilisé par le client de l'API dans les entête HTTP.
  • A partir du token notamment on saura à quoi peut accéder tel ou tel membre.

L'authentification se base sur l'utilisation d'access token et de refresh token :

1
2
3
4
5
6
{
  "access_token": "<your-access-token>", 
  "scope": "read", 
  "expires_in": 86399, 
  "refresh_token": "<your-refresh-token>"
}
  1. L'utilisateur se log pour la première fois grâce à un client à l'API
  2. Le serveur lui renvoi son access token avec son expiration et son refresh token sans expiration
  3. Durant la session, l'access token est utilisé dans les requêtes
  4. A chaque fois que l'access token expire, le client refait une authentification avec le refresh token et la clé secrète

Ce processus a plusieurs intérêts :

  • Le token d'accès est le maillon faible, s'il est intercepté, tu peux prendre la place de l'utilisateur. En limitant sa durée de vie à une "session d'utilisation" (en gros inactivité pendant quelques minutes => expiration) voire en limitant sa durée de vie tout court à quelques minutes, tu limites le danger.

  • L'application n'a pas besoin de stocker le login et le mot de passe de l'utilisateur, et ne devrait pas le faire . Et c'est bien mieux comme ça pour tout le monde. (si jamais l'application est compromise, faille de sécurité, …)

  • Si la réponse du serveur contenant l'accessToken et le refreshToken est interceptée, l'accessToken va pouvoir être utilisé (mais c'est limité dans le temps… même si c'est extrêmement dangereux) par contre pour le refreshToken il faut le client_secret. Qui n'est pas dans la réponse du serveur.

  • Permet également à un client, n'importe où, n'importe quand d'invalider des accessToken, sans avoir besoin de saisir à nouveau ses login / mot de passe sur tous ses appareils. En gros, je dis "oulà, y'a une activité suspecte sur mon compte, un de mes accessToken a été intercepté, invalide les tous"

Edit : Effectivement Javier. L'implémentation de l'authentification OAuth2 dans Django respecte la spec et permet de créer deux types de client.

+0 -0

Toutes ces questions (authentification, refresh, inscription, etc.) devrait être disponible dans la doc. Ce n'est pas pour être chiant, mais on a déjà expérimenté le "mergeons d'abord ça, la doc arrivera plus tard", et visiblement ça ne marche pas chez nous.

Tout le monde était a peu près d'accord pour dire que "chaque nouvelle fonctionnalité doit avoir toute sa documentation avec la PR", et c'est encore plus important dans le cas d'une API.

Toutes ces questions (authentification, refresh, inscription, etc.) devrait être disponible dans la doc. Ce n'est pas pour être chiant, mais on a déjà expérimenté le "mergeons d'abord ça, la doc arrivera plus tard", et visiblement ça ne marche pas chez nous.

Ceci n'a jamais été dit sur ce sujet et ne sera jamais dit !

Tout le monde était a peu près d'accord pour dire que "chaque nouvelle fonctionnalité doit avoir toute sa documentation avec la PR", et c'est encore plus important dans le cas d'une API.

L'ajout d'information dans la PR actuelle devrait arriver ce soir si je ne rencontre pas de problèmes.

Bien, si vous êtes intéressé de savoir ce qui figure dans la documentation, la PR a été complétée pour rajouter de la documentation dans sphinx et pour rajouter de l'information dans la documentation générée par swagger. En fait, maintenant, swagger affiche toutes les informations disponibles sur mon message récapitulatif.

Qu'en pensez-vous ?

Sachez aussi que nous sommes toujours à la recherche de revue de code pour la PR et d'une solution pour tester automatiquement l'API (en plus des tests développés). :)

Sachez aussi que nous sommes toujours à la recherche de revue de code pour la PR et d'une solution pour tester automatiquement l'API (en plus des tests développés). :)

On a quoi comme solution pour tester l'API ? Il vous faut quoi ? Un module Python pour faire les requetes à l'API comme tout client ?

Cette proposition vient de Javier, soutenue par firm1, qui propose de développer un projet (ou d'en trouver un) pour tester l'API et de l'enrichir à chaque fois que de nouvelles routes apparaissent.

Personnellement, en l'état, je ne vois pas la différence avec les tests que j'ai intégré mais il faut sans doute en discuter ici et prendre une décision à ce sujet.

En gros c'est faire les mêmes tests que toi mais en passant par une communication réel client/serveur, non ?

BTW, tes tests m'ont déjà l'air bien complet. On pourra rajouter ce genre de tests quand des clients auront été dévéloppés. Sinon, avec requests, je peux faire une mini lib python client rapidement je pense, si je dégage du temps pour installer ta branche en local.

Selon moi, cela fera aussi redondance avec les tests puisque le module de tests de DRF se contente d'envoyer des requêtes au serveur aussi (comme un client). Alors j'aimerais bien l'intervention de Javier (ou firm1) pour expliciter un peu ce qu'ils voudraient en plus.

Sinon, ce soir je corrige un bug lié à la pagination puis je pense avoir résolu tous vos retours, excepté la redondance dans le CSS front de la documentation générée par Swagger où il me faut l'intervention d'un développeur front.

L'idée que j'avais en tête c'est créer un client HTTP (dans le langage que vous voulez) qui dispose de primitives de ce type :

  • createAccount

  • login (via OAuth)

  • getMyInfos (récupère son profil)

Effectivement, ça semble faire doublon avec les TU que tu as développés.

Pourquoi je propose ça ?

  1. J'ai tendance, pour une API, à favoriser les tests d'intégration plutôt que des tests unitaires (souvent je fais les deux, mais si j'ai un choix à faire je favorise les tests d'intégration) il m'est arrivé parfois d'avoir des saletés que je ne voyais pas avec les TU (des headers pas bien gérés, un scénario qui se déroule mal parce qu'une requête s'est "perdue", …). En gros, niveau TU, je vais tester la pagination mais pas comme tu l'as fait : en gros je vais faire un test agnostique du module (membres ou autres) je vais juste donner à manger à mon paginateur une grosse liste et vérifier qu'il y a le nombre de pages attendu. Par contre quand je vais tester un module en particulier (comme ici) là je vais favoriser les TI de bout en bout, pour éviter les mauvaises surprises.

  2. Ca donne les bases d'un client simple (une implémentation basique) pour des gens qui voudraient l'écrire en Python. Et ça me paraît très intéressant dans un projet Open Source. Forkez ce projet, mettre votre CLIENT_SECRET ici et vous avez un canvas autour duquel construire votre appli.

  3. Ca sert de doc technique. Ca peut paraître con mais pour un développeur, lire du code qui fonctionne ça rassure et ça permet parfois de mieux comprendre "attends mais comment il récupère la seconde page, je pige pas" => "à OK, il lit tel header". Je ne compte plus les fois où, pour coder un client en JS je suis allé lire des TI écrits en Groovy et j'ai squizzé la doc. Ca m'a souvent fait gagner un temps précieux.

Voilà pour les arguments. Après c'est vrai qu'en regardant les tests que tu as écrits, ça va très fortement se ressembler, à vous de choisir l'approche qui vous plaît le plus.

PS (un argument supplémentaire) : je ne sais pas très bien comment fonctionne DRF, mais ce qui me semble essentiel, c'est de pouvoir faire tourner les tests d'intégration depuis n'importe quelle machine client, en attaquant n'importe quel serveur (prod, préprod, local, …). C'est beaucoup plus souple. Ca peut permettre notamment à n'importe qui de détecter un bug depuis sa machine en préprod par exemple, plutôt que d'être obligé de tout installer (idem pour les tests avec browser embarqué d'ailleurs, c'est le même principe).

+2 -0

Je comprends mieux ce que tu voulais dire. Dans mon ancienne boite, j'ai fais quelque chose d'assez similaire. J'ai développé des tests en Java qui interrogeaient l'API d'un prestataire (dont je tairais le nom) qui fournissait une API vraiment horrible (il se torchait le cul avec toutes les bonnes pratiques), qui tombait d'un jour à l'autre et qui évoluait très rapidement (sans versionning bien entendu). Dans ce cas présent, développer ce projet pour tester cette API était très bénéfique pour plusieurs raisons :

  • Tous les développeurs dans la boite pouvait consulter ce projet pour avoir un exemple compréhensible et simple de la façon dont il fallait interroger une route donnée.
  • Cela nous permettait d'accuser ce prestataire plutôt que nous puisque le client nous accusait à chaque fois (nous développions le côté client).

Où je veux en venir ? Dans ce cas précis, je trouve ces tests d'intégration vraiment bénéfiques puisqu'il y a une réelle raison derrière. Dans notre cas, je trouve que la nécessité de cette solution beaucoup moins pertinente pour tester l'API. Je suis bien plus convaincu pour une documentation technique en proposant un client de "référence" mais cela devient un projet à part qui ne doit pas être lié aux tests de l'API (où du moins, pas directement et sans doute pas maintenant).

Après, cela reste mon avis et j'attends d'autres avis sur la question.

Dans une API plus les tests sont de haut niveau, mieux c'est en général. Là typiquement les tests unitaires qui sont écrit actuellement couvrent déjà pas mal le truc, mais le problème des tests unitaires c'est qu'on a du mal à avoir un enchainement des tout celà.

C'est typiquement le genre de truc ou j'aurai tendance à coller un lettuce ou un behave qui ne ferait qu'écrire des instructions du genre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Feature: Test api member

  Scenario: Give me a member list
    Given I have the following members in my database:
      | username     | password    | role       |
      | user1        | user1       | member     |
      | user2        | user2       | staff      |
      | user3        | user3       | superuser  |
    When I access the url "/membres/"
    Then Status of request should be "200"
    And Result of request should be "[{"username":"user1", "role":"member"}, {"username":"user2", "role":"staff"}, {"username":"user3", "role":"superuser"}]"
    When I access the url "/membres/?page=2"
    Then Status of request should be "404"
    When I access the url "/membres/?id=1"
    Then Status of request should be "200"
    And Result of request should be "[{"username":"user1", "role":"member"}]"

C'est typiquement le niveau le plus adapté pour des tests API du genre. Et là, normalement, impossible de rater un cas de test.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte