Oauthlib: L'application Web cliente n'envoie plus client_id

Créé le 8 sept. 2018  ·  26Commentaires  ·  Source: oauthlib/oauthlib

J'ai trouvé une régression dans master lorsqu'il est utilisé avec request/requests-oauthlib puisque https://github.com/oauthlib/oauthlib/issues/495 a été fusionné. Il est lié à l'octroi d'autorisation/à la demande Web uniquement.

L'utilisation de base de request-oauthlib est :

sess = OAuth2Session(client_id)
token = sess.fetch_token(token_url, client_secret=client_secret, authorization_response=request.url)

Cependant, depuis les changements, client_id de la session est ignoré. Je pense que https://github.com/oauthlib/oauthlib/pull/505 a corrigé un cas d'utilisation mais en a cassé un autre. Nous devons trouver une solution gagnant-gagnant.

Requests-oauthlib appel de code à https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196 -L198 et oauthlib question ici
https://github.com/oauthlib/oauthlib/blame/master/oauthlib/oauth2/rfc6749/clients/web_application.py#L128.

Bug Discussion OAuth2-Client

Commentaire le plus utile

Je ne verrais pas comment vous voudriez remplacer client_id avec une valeur différente, alors voteriez pour lever une exception si elles diffèrent.

Devrions-nous, en plus, enregistrer un DeprecationWarning si client_id était fourni en tant que kwarg ?

Tous les 26 commentaires

Ma première réflexion est de revenir sur les modifications apportées à prepare_request_body pour, par défaut, utiliser le self.client_id défini dans le constructeur WebApplicationClient .
Ensuite, les documents en ligne doivent être modifiés en conséquence pour ajouter &client_id=xx à la sortie prepare_request_body .

Enfin, pour remplacer le correctif d'origine, je suggère de supprimer client_id des arguments et d'ajouter un nouvel argument à prepare_request_body comme include_client=True/False pour ajouter à la fois client_id et client_secret dans le corps, ou ne pas les inclure tous les deux.

Les pensées?

poke @Diaoul @skion @thedrow

Qu'en est-il de:

Je suggérerai de supprimer client_id des arguments et d'ajouter un nouvel argument à prepare_request_body comme include_client=True/False pour ajouter à la fois client_id et client_secret dans le corps, ou ne pas les inclure tous les deux.

Merci

En fait, j'ai déclenché ce même problème dans l'un de mes tests, mais je l'ai déposé contre request/oauthlib ici : https://github.com/requests/requests-oauthlib/issues/330

Je crois que le problème est en fait la faute de request_oauthlib. Leurs documents - en fait le premier exemple dans l'ensemble de leurs documents lorsque vous chargez la page - prennent en charge la spécification du client_id dans le constructeur OAuth2Session . La logique de la ligne 200 extrait le client_id des kwargs, mais n'a pas de solution de repli pour l'extraire de l'instance WebApplicationClient déjà construite.

@jvanasco , le problème actuel n°585 peut être résolu dans request-oauthlib uniquement, ou en inversant le PR n°505 d'oauthlib. Cependant, aucune des solutions ne corrigera le comportement mentionné par @skion dans son commentaire sur https://github.com/oauthlib/oauthlib/pull/505#issuecomment -351221107

Selon la spécification, le paramètre client_id doit être envoyé pour les clients non authentifiés, mais n'est de préférence PAS envoyé dans le corps de la demande de jeton pour les clients confidentiels, car dans ce cas, le mécanisme préféré pour authentifier le client est via HTTP Basic auth. Cependant, la classe WebApplication l'inclut toujours (ce qui casse certains serveurs) et n'offre aucun mécanisme pour le supprimer.

Oauthlib doit fournir un moyen élégant et simple pour que request-oauthlib résolve le problème. Si nous pouvons trouver une solution dans cette discussion, ce sera formidable ; parce que c'est un énorme bloqueur.

Le fait d'autoriser client_id=False dans prepare_request_body() aiderait-il à indiquer qu'un client_id ne doit pas être envoyé ? Bien que même si c'était le cas, cela conduirait à cette laideur près de la ligne 126 :

client_id = None if client_id=False else self.client_id

Ah, je vois.

Existe-t-il un test unitaire indiquant quand client_id doit être envoyé ou non ? Si non, quelqu'un a-t-il une liste qui peut être utilisée pour en générer une ? Je serais heureux de tenter de résoudre ce problème et de demander-oauthlib, car cela bloque une partie de mon travail en ce moment.

@JonathanHuot

Je suggérerai de supprimer client_id des arguments et d'ajouter un nouvel argument à prepare_request_body comme include_client=True/False pour ajouter à la fois client_id et client_secret dans le corps, ou ne pas les inclure tous les deux.

En relisant ceci, j'aime bien votre suggestion. Nous pouvons probablement nous en sortir de manière ininterrompue, car la fonction prend déjà **kwargs .

Une note:

ajouter à la fois client_id et client_secret dans le corps

Puisqu'il s'agit de clients publics IIUC, je ne pense pas que le client_secret soit impliqué; c'est juste le client_id ajouté au corps ? Dans ce cas, j'envisagerais également de renommer le nouveau paramètre en include_client_id=True/False .

Dans ce cas, j'envisagerais également de renommer le nouveau paramètre en include_client_id=True/False.

En effet ! client_secret n'est pas impliqué car il n'est pas présent dans WebApplicationClient .

@jvanasco , si vous voulez faire un PR, je pense que les changements devraient être :
1) Rétablir https://github.com/oauthlib/oauthlib/pull/505
2) Modifiez la signature de prepare_request_body() pour supprimer client_id et ajoutez include_client_id=True/False ( True (par défaut) : cela ajoute self.client_id )

Ensuite, request-oauthlib aura le choix dans https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196-L211 soit :
A) Inclure client_id seul dans le corps

self._client.prepare_request_body(..)

B) Inclure client_id et client_secret dans auth et ne pas les inclure dans le corps (solution préférée RFC)

self._client.prepare_request_body(include_client_id=False, ..)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)

C) Inclure client_id et client_secret dans le corps (solution alternative RFC)

self._client.prepare_request_body(client_secret=client_secret, ..)

Je vais générer des relations publiques pour les deux projets aujourd'hui.

J'ai à peu près un PR et des tests effectués pour OAuthlib. Cependant j'ai une question...

client_id devrait-il toujours être autorisé en tant que kwarg ? C'est en partie pour la compatibilité descendante, mais aussi pour les cas extrêmes. Parce que cette méthode était quelque peu cassée, je pense qu'il peut être utile de la faire fonctionner comme prévu (comme en lui permettant dans prepare_request_body de remplacer le self.client_id) ou de lever une exception sur une utilisation incorrecte (comme dans la levée une erreur si client_id est fourni mais ne correspond pas à self.client_id ).

Je ne verrais pas comment vous voudriez remplacer client_id avec une valeur différente, alors voteriez pour lever une exception si elles diffèrent.

Devrions-nous, en plus, enregistrer un DeprecationWarning si client_id était fourni en tant que kwarg ?

PR #593 soumis. Il lève un DeprecationWarning lorsque client_id est soumis, et une ValueError s'il diffère de self.client_id . Il y a aussi un nouveau test qui assure le respect des trois scénarios détaillés @JonathanHuot .

rencontré mon premier problème avec le candidat de request-oauthlib PR alors que j'écris quelques tests

Il invoque TOUJOURS prepare_request_body avec username=username, password=password . Cela semble faux. J'espère que quelqu'un ici connaît mieux la RFC et connaît les réponses aux questions suivantes :

  1. un username + password devrait-il être un paramètre dans le request.body ?
  2. si les username + password apparaissent dans l'en-tête HTTP Basic Auth, doivent-ils être dupliqués dans le corps de la requête ?
  3. Est-il possible d'avoir à la fois un username + password body params et un en-tête HTTPBasicAuth avec les détails du client ?

Merci d'avoir rencontré ça.

  1. username et password doivent toujours être présents dans le corps de la requête. Ils ne doivent être utilisés que pour l'octroi de mot de passe, c'est-à-dire l'héritage. Ceux-ci ne doivent pas être utilisés pour d'autres attributions (implicite, code, identifiants client).
  2. HTTP Basic Auth est facultatif pour les informations d'identification du client (recommandé pour les clients confidentiels, c'est-à-dire avec un client_secret ). Les informations d'identification de l'utilisateur ne doivent
  3. Oui

Merci. Juste pour clarifier deux choses, et n'hésitez pas à me parler comme si j'avais cinq ans. Je veux m'assurer que j'obtiens ceci et que les tests sont corrects :

  1. Le nom d'utilisateur et le mot de passe ne sont utilisés que dans un certain type de subvention qui les nécessite. S'ils sont utilisés, ils ne peuvent être présents que dans le corps de la requête.

À moins qu'ils ne soient explicitement spécifiés, ils ne devraient pas être présents, n'est-ce pas ?

  1. Si l'authentification de base Http ne concerne que les informations d'identification du client, la bibliothèque de requêtes existante ne devrait pas avoir le bloc qui génère une authentification de base à partir de la combinaison nom d'utilisateur et mot de passe.

Veuillez m'excuser d'être bavard et d'être obsédé par ces petits détails. Je veux juste m'assurer que j'obtiens le bon comportement et que je peux écrire des tests qui garantissent que nous n'obtenons pas une autre régression.

@jvanasco , je peux parler de la RFC OAuth2, mais je ne sais pas comment les requests-oauthlib et flask-oauthlib s'emboîtent.

  1. Oui

Correct.

  1. Oui, AFAIU il ne devrait pas avoir ce bloc https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L207 -L211.

C'est ce que je comprends, mais il sera bon de se rapprocher de la réalité du terrain; c'est-à-dire les requêtes-oauthlib et les différentes expériences des fournisseurs publics. Discussions sur plusieurs requêtes-oauthlib https://github.com/requests/requests-oauthlib/issues/218 , https://github.com/requests/requests-oauthlib/issues/211 , https://github.com/requests /requests-oauthlib/issues/264 , déjà arrivé.

Je crois qu'ils ont eu une confusion entre client password et client secret qui sont en fait deux formulations pour exactement la même chose.
Si nous suivons le raisonnement derrière https://github.com/requests/requests-oauthlib/pull/206 , le contenu du PR n'aurait jamais dû être comme ajouter HTTPAuth(username, password) mais il aurait dû être HTTPAuth(client_id, client_secret (le mot de passe du client).

Poke @Lukasa , @chaosct , @ibuchanan qui a participé aux discussions de request-oauthlib.

Super! Merci beaucoup. Je pensais que c'était ce qui se passait mais je voulais confirmer.

Je pense que je sais comment je veux structurer les requêtes maintenant. J'ai une poignée de commits sur le projet de requêtes principales, donc je sais ce que les mainteneurs aiment voir dans les PR et les fonctionnalités.

  1. Le nom d'utilisateur et le mot de passe ne sont utilisés que dans un certain type de subvention qui les nécessite. S'ils sont utilisés, ils ne peuvent être présents que dans le corps de la requête.

Oui, à la première partie : seul un certain type de bourse les nécessite. Mais sur la 2ème partie concernant leur envoi dans le corps de la requête, la spécification dit :

Inclure les informations d'identification du client dans le corps de la requête
l'utilisation des deux paramètres n'est PAS RECOMMANDÉE
et DEVRAIT être limité aux clients incapables d'utiliser directement
le schéma d'authentification HTTP Basic...

Mais pour les serveurs, il lit :

Le serveur d'autorisation PEUT prendre en charge y compris
les informations d'identification du client dans le corps de la requête...

Un client conforme n'enverrait pas les informations d'identification dans le corps de la demande.
Mais, pour certains serveurs partiellement implémentés, ils ne les accepteront que dans le corps de la requête.
Si je me souviens bien, mon PR a résolu cette confusion en ajoutant l'en-tête auth,
le client envoie donc les deux.

Je crois que @JonathanHuot a raison sur le 2ème point.

Salut @ibuchanan , les citations client credentials . Nous devons faire très attention à ne pas mélanger le client avec le propriétaire de la ressource (l'utilisateur réel).

Les rôles sont clairement expliqués ici rfc6749#1.1 .
Ce client credentials fait référence à client_id et client_secret et le propriétaire de la ressource fait référence à username et password . Ceux-ci ne sont pas interchangeables.

Ainsi, en lisant la RFC avec ces rôles, cela signifie qu'un client conforme DEVRAIT envoyer les informations d'identification du client ( client_id , client_secret ) en HTTP Basic et DOIT envoyer les informations d'identification de l'utilisateur ( username , password ) dans le corps de la requête (ne jamais lire d'alternative ici); voir rfc6749#4.3.2 .

Certains serveurs rejettent la demande (400) si le client_id est dans la demande
corps. Je pense que la valeur par défaut devrait être ce qui est recommandé par la spécification.

D'accord. Je pense que le PR actuel pour oauthlib répond aux préoccupations ci-dessus - le drapeau include_client_id permet explicitement d'envoyer ou non le client_id .

en termes de requests_oauthlib ,

  1. username et password n'apparaîtront que dans le corps (pas en tant que HTTP Basic). Si ce comportement est nécessaire pour une intégration de serveur non conforme, l'implémenteur peut soumettre un argument auth ou headers à fetch_token() .

  2. fournir les client_id au bon endroit est un peu ennuyeux, mais je pense que j'ai la logique et les cas d'utilisation. cela aura certainement besoin d'un examen.

Une question que j'ai pour @JonathanHuot et @ibuchanan :

oauthlib OAuth2 Client et requests_oauthlib de OAuth2Session ne conservent pas le client_secret et doivent l'invoquer à plusieurs reprises. Ce n'était pas le cas dans OAuth1, et je pense que c'était le vrai problème derrière #370. Le RFC n'a pas mentionné la nécessité de cela, et je n'ai trouvé aucun historique.

Pour moi, il est logique d'étendre le client en stockant le client_secret , et de commencer à déprécier la dépendance OAuth2Session en transmettant le client_secret sur fetch_token et request en faveur de l'utilisation de celui maintenant dans le client lui-même. (cela résoudrait également certaines autres incohérences signalées dans https://github.com/requests/requests-oauthlib/issues/264 )

J'ai légèrement modifié le PR pour oauthlib (#593) pour normaliser les variables de test pour le nom d'utilisateur/mot de passe et client_id/client_secret. Je pense que cela devrait éviter les erreurs de confusion entre les deux à l'avenir.

Le changement proposé pour les requêtes-oauthlib : https://github.com/requests/requests-oauthlib/compare/master...jvanasco :fix-oauthlib_client_id

Celui-ci est un peu plus drastique, car en regardant le code et les tests, il semble que la bibliothèque essayait simplement de faire fonctionner toutes sortes de choses qui ne devraient pas fonctionner.

Le correctif fait plusieurs choses :

  1. La vérification de username et password ne se produit que pour les instances LegacyApplicationClient -- car cela devrait être le seul type nécessaire (correct ?). Il y a une section sous la vérification qui fusionne le nom d'utilisateur/mot de passe dans le dict kwargs. Il est actuellement en retrait, car les tests suggèrent que d'autres clients pourraient vouloir transmettre cette information.

  2. La logique de gestion des en-têtes client_id/auth a été réécrite pour garantir que les bons types d'authentification se produisent au bon endroit par défaut. Si un utilisateur souhaite forcer les informations d'identification d'une autre manière, cela reste possible.

  3. Question : Existe-t-il une situation dans laquelle client_secret ne serait pas transmis ? Je n'en vois aucun, mais il existe de nombreux flux oAuth.

Donc dans la version request-oauthlib :

| include_client_id | auth | comportement |
| ------------------- | --------------- | -------- |
| Aucun (par défaut) | Aucun (par défaut) | créez un objet Auth avec le client_id . N'envoyez pas le client_id dans le corps. Il s'agit du comportement par défaut, car le RFC le recommande. |
| Aucun (par défaut) | présent | utilisez l'objet Auth. invoquer les prepare_request_body d'oauthlib avec include_client_id=False |
| Faux | présent | utilisez l'objet Auth. invoquer les prepare_request_body d'oauthlib avec include_client_id=False |
| Vrai | présent | utilisez l'objet Auth. invoquer les prepare_request_body d'oauthlib avec include_client_id=True |
| Vrai | Aucun (par défaut) | créez un objet Auth avec le client_id . invoquer les prepare_request_body d'oauthlib avec include_client_id=True |
| Faux | Aucun (par défaut) | créez un objet Auth avec le client_id . invoquer les prepare_request_body d'oauthlib avec include_client_id=False |

ou indiqué autrement :

  • créer un objet d'authentification

    • n'envoyez pas l'identifiant client dans le corps :

    • (include_client_id=Aucun, auth=Aucun)

    • (include_client_id=False, auth=Aucun)

    • envoyer l'identifiant client dans le corps :

    • (include_client_id=True, auth=None)

  • utiliser un objet d'authentification explicite

    • n'envoyez pas le client_id dans le corps :

    • (include_client_id=Aucun, auth=authObject)

    • (include_client_id=False, auth=authObject)

    • envoyer le client_id dans le corps :

    • (include_client_id=True, auth=authObject)

@jvanasco : semble très bien du côté oauthlib et request-oauthlib.

À propos de votre 3. question :

Vous pouvez avoir des clients publics sans client_secret (ou vide, c'est proche d'un point de vue RFC) ; donc l'API python doit prendre en charge "pas de secret".

Le cas d'utilisation réel est souvent pour les applications natives où vous préférez utiliser le code d'autorisation, mais vous ne pouvez pas garantir la sécurité autour de garder le secret en sécurité, donc, soit vous acceptez un client_id sans client_secret (ou vide client_secret qui sont identiques pour le RFC); ou vous disposez également d'un autre RFC PKCE (voir https://oauth.net/2/pkce/ ). Mais ce dernier n'est pas encore implémenté du côté de oauthlib .

Merci. Je vais ajouter quelques cas de test pour m'assurer que nous pouvons soumettre client_id sans client_secret . Désolé d'avoir posé tant de questions, il y a tellement d'implémentations correctes possibles de cette spécification (et encore plus d'implémentations cassées qui doivent fonctionner).

@JonathanHuot l'implémentation existante ne prend pas en charge l'envoi d'une chaîne vide pour client_secret . Il est supprimé dans cette logique https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth2/rfc6749/parameters.py#L90 -L125 -- spécifiquement ligne 122

Le soutenir peut être l'ajout de quelque chose comme ceci juste après cette routine :

if ('client_secret' in kwargs) and ('client_secret' not in params):
    if kwargs['client_secret'] == '':
        params.append((unicode_type('client_secret'), kwargs['client_secret']))

Cela enverrait une chaîne vide pour client_secret lorsque le secret est une chaîne vide, mais n'enverrait pas le client_secret si la valeur est None .

Je pense que cela vaut la peine de le soutenir, car si la RFC prend en charge l'une ou l'autre des deux variantes... il y aura probablement de nombreuses implémentations cassées qui ne prennent en charge qu'une seule variante.

Ce problème d'origine est résolu dans oauthlib. Cependant, le comportement est toujours là jusqu'à ce que https://github.com/requests/requests-oauthlib/pull/331 soit corrigé.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

prudnikov picture prudnikov  ·  11Commentaires

JonathanHuot picture JonathanHuot  ·  10Commentaires

JonathanHuot picture JonathanHuot  ·  15Commentaires

potiuk picture potiuk  ·  14Commentaires

thedrow picture thedrow  ·  31Commentaires