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.
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 :
username
+ password
devrait-il être un paramètre dans le request.body ?username
+ password
apparaissent dans l'en-tête HTTP Basic Auth, doivent-ils être dupliqués dans le corps de la requête ?username
+ password
body params et un en-tête HTTPBasicAuth avec les détails du client ?Merci d'avoir rencontré ça.
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).client_secret
). Les informations d'identification de l'utilisateur ne doivent 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 :
À moins qu'ils ne soient explicitement spécifiés, ils ne devraient pas être présents, n'est-ce pas ?
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.
Correct.
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.
- 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
,
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()
.
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 :
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.
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.
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 :
@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é.
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
siclient_id
était fourni en tant que kwarg ?