Oauthlib: Client-Webanwendung sendet client_id nicht mehr

Erstellt am 8. Sept. 2018  ·  26Kommentare  ·  Quelle: oauthlib/oauthlib

Ich habe eine Regression in master gefunden, wenn es mit Requests/requests-oauthlib verwendet wurde, da https://github.com/oauthlib/oauthlib/issues/495 zusammengeführt wurde. Es bezieht sich nur auf die Berechtigungserteilung/Webanwendung.

Die grundlegende Verwendung von request-oauthlib ist:

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

Seit den Änderungen wird jedoch client_id der Sitzung ignoriert. Ich denke, https://github.com/oauthlib/oauthlib/pull/505 hat einen Anwendungsfall behoben, aber einen anderen kaputt gemacht. Wir sollten eine Win-Win-Lösung finden.

request-oauthlib-Codeaufruf unter https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196 -L198 und oauthlib-Problem hier
https://github.com/oauthlib/oauthlib/blame/master/oauthlib/oauth2/rfc6749/clients/web_application.py#L128.

Bug Discussion OAuth2-Client

Hilfreichster Kommentar

Ich würde nicht sehen, wie Sie client_id jemals mit einem anderen Wert überschreiben möchten, und würden daher für das Auslösen einer Ausnahme stimmen, wenn sie sich unterscheiden.

Sollen wir zusätzlich ein DeprecationWarning protokollieren, wenn client_id überhaupt als Kwarg bereitgestellt wurde?

Alle 26 Kommentare

Mein erster Gedanke ist, die Änderungen in prepare_request_body zu machen, um standardmäßig den self.client_id Satz im WebApplicationClient Konstruktor zu verwenden.
Dann sollten Inline-Dokumente entsprechend geändert werden, um &client_id=xx zur Ausgabe von prepare_request_body hinzuzufügen.

Um den ursprünglichen Fix zu ersetzen, schlage ich schließlich vor, client_id aus den Argumenten zu entfernen und ein neues Argument zu prepare_request_body wie include_client=True/False hinzuzufügen, um beide client_id hinzuzufügen client_secret im Textkörper enthalten oder nicht beide enthalten.

Die Gedanken?

stocher @Diaoul @skion @thedrow

Wie wäre es mit:

Ich schlage vor, client_id aus den Argumenten zu entfernen und ein neues Argument zu Prepare_request_body hinzuzufügen, wie include_client=True/False, um sowohl client_id als auch client_secret in den Body hinzuzufügen oder nicht beide einzuschließen.

Vielen Dank

Ich habe das gleiche Problem tatsächlich in einem meiner Tests ausgelöst, es aber hier gegen Requests/oauthlib eingereicht: https://github.com/requests/requests-oauthlib/issues/330

Ich glaube, das Problem liegt tatsächlich an Requests_oauthlib. Ihre Dokumente – eigentlich das erste Beispiel in ihren gesamten Dokumenten, wenn Sie die Seite laden – unterstützen die Angabe von client_id im OAuth2Session Konstruktor. Die Logik in Zeile 200 zieht client_id von den Kwargs, hat aber keinen Fallback, um es aus der bereits konstruierten WebApplicationClient Instanz zu ziehen.

@jvanasco , das aktuelle Problem #585 kann allein in Requests-oauthlib oder durch Zurücksetzen von oauthlibs PR #505 behoben werden. Keine der Lösungen wird jedoch das von @skion in seinem Kommentar unter https://github.com/oauthlib/oauthlib/pull/505#issuecomment -351221107 erwähnte Verhalten beheben

Gemäß der Spezifikation muss der Parameter client_id für nicht authentifizierte Clients gesendet werden, wird jedoch für vertrauliche Clients vorzugsweise NICHT im Token-Anforderungstext gesendet, da in diesem Fall der bevorzugte Mechanismus zur Authentifizierung des Clients über die HTTP Basic-Authentifizierung erfolgt. Die WebApplication-Klasse enthält es jedoch immer (was einige Server zerstört) und bietet keinen Mechanismus zum Entfernen.

Oauthlib muss eine elegante und einfache Möglichkeit für Requests-oauthlib bieten, das Problem zu lösen. Wenn wir in dieser Diskussion eine Lösung finden, ist das großartig; weil das ein riesiger Blocker ist.

Würde es helfen, client_id=False in prepare_request_body() zuzulassen, um anzuzeigen, dass keine client_id gesendet werden soll? Obwohl dies in der Nähe von Zeile 126 zu dieser Hässlichkeit führen würde:

client_id = None if client_id=False else self.client_id

Ah ich sehe.

Gibt es einen vorhandenen Unit-Test, wann client_id gesendet werden soll oder nicht? Wenn nicht, hat jemand eine Auflistung, die verwendet werden kann, um eine zu erstellen? Ich würde gerne versuchen, dies und die Requests-oauthlib zu reparieren, da es gerade einen Teil meiner Arbeit blockiert.

@JonathanHuot

Ich schlage vor, client_id aus den Argumenten zu entfernen und ein neues Argument zu Prepare_request_body hinzuzufügen, wie include_client=True/False, um sowohl client_id als auch client_secret in den Body hinzuzufügen oder nicht beide einzuschließen.

Wenn ich das nochmal lese, finde ich deinen Vorschlag eigentlich ganz gut. Wir können es wahrscheinlich auf nicht brechende Weise tun, da die Funktion bereits **kwargs .

Eine Note:

um sowohl client_id als auch client_secret im Körper hinzuzufügen

Da es sich hier um öffentliche Auftraggeber IIUC handelt, würde ich nicht glauben, dass client_secret beteiligt ist; es sind nur die client_id , die dem Körper hinzugefügt werden? In diesem Fall würde ich auch in Erwägung ziehen, den neuen Parameter in include_client_id=True/False .

In diesem Fall würde ich auch in Erwägung ziehen, den neuen Parameter in include_client_id=True/False umzubenennen.

In der Tat ! client_secret ist nicht beteiligt, da es in WebApplicationClient nicht vorhanden ist.

@jvanasco , wenn Sie eine PR machen möchten, sollten die Änderungen meiner Meinung nach sein:
1) Zurücksetzen https://github.com/oauthlib/oauthlib/pull/505
2) Ändern Sie die Signatur von prepare_request_body() , um client_id prepare_request_body() zu entfernen und include_client_id=True/False hinzuzufügen ( True (Standard): es fügt self.client_id )

Requests-oauthlib hat dann in https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196-L211 die Wahl zwischen:
A) Fügen Sie client_id allein in den Körper ein

self._client.prepare_request_body(..)

B) Fügen Sie client_id und client_secret in auth und fügen Sie sie nicht in den Textkörper ein (RFC-bevorzugte Lösung)

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

C) Fügen Sie client_id und client_secret in den Körper ein (RFC-Alternativlösung)

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

Ich werde heute PR's für beide Projekte generieren.

Ich habe so ziemlich eine PR und Tests für OAuthlib durchgeführt. Ich habe aber eine Frage...

Sollte client_id noch als Kwarg erlaubt sein? Dies dient zum Teil der Abwärtskompatibilität, aber auch für Randfälle. Da diese Methode etwas kaputt war, denke ich, dass es sich lohnen kann, sie entweder wie beabsichtigt zu arbeiten (wie in prepare_request_body erlaubt, die self.client_id zu überschreiben) oder eine Ausnahme bei falscher Verwendung auszulösen (wie in ein Fehler, wenn client_id wird, aber nicht mit self.client_id übereinstimmt).

Ich würde nicht sehen, wie Sie client_id jemals mit einem anderen Wert überschreiben möchten, und würden daher für das Auslösen einer Ausnahme stimmen, wenn sie sich unterscheiden.

Sollen wir zusätzlich ein DeprecationWarning protokollieren, wenn client_id überhaupt als Kwarg bereitgestellt wurde?

PR #593 eingereicht. Es löst eine DeprecationWarning aus, wenn client_id gesendet wird, und einen ValueError, wenn es sich von self.client_id . Außerdem gibt es einen neuen Test, der die Einhaltung der drei Szenarien sicherstellt, die

stieß auf mein erstes Problem mit Anfragen – oauthlib PR-Kandidat, als ich einige Tests schreibe

Es ruft prepare_request_body IMMER mit username=username, password=password . Dies scheint falsch zu sein. Ich hoffe, jemand hier ist mit dem RFC besser vertraut und kennt die Antworten auf die folgenden:

  1. sollte ein username + password überhaupt ein Parameter im request.body sein?
  2. Wenn username + password im HTTP Basic Auth-Header erscheinen, sollten sie dann im Anforderungstext dupliziert werden?
  3. Ist es möglich, sowohl username + password Body-Parameter als auch einen HTTPBasicAuth-Header mit den Client-Details zu haben?

Danke, dass du darauf gestoßen bist.

  1. username und password müssen immer im Hauptteil der Anfrage vorhanden sein. Sie dürfen nur für die Passworterteilung, auch bekannt als Legacy, verwendet werden. Diese dürfen nicht für andere Grants (implizit, Code, Client-Anmeldeinformationen) verwendet werden.
  2. HTTP Basic Auth ist optional für Client-Anmeldeinformationen (empfohlen für vertrauliche Clients, zB mit einem client_secret ). Benutzeranmeldeinformationen dürfen niemals in HTTP Basic Auth enthalten sein.
  3. Jawohl

Vielen Dank. Nur um zwei Dinge klarzustellen, und zögern Sie nicht, mit mir zu sprechen, als wäre ich fünf. Ich möchte sicherstellen, dass ich dies und die Tests richtig mache:

  1. Benutzername und Passwort werden nur in einer bestimmten Art von Erteilung verwendet, die sie erfordert. Wenn sie verwendet werden, dürfen sie nur im Anfragetext vorhanden sein.

Sofern sie nicht ausdrücklich angegeben sind, sollten sie nicht vorhanden sein, richtig?

  1. Wenn die Http-Basisauthentifizierung nur für Client-Anmeldeinformationen gilt, sollte die vorhandene Requests-oauthlib nicht den Block haben, der eine Basisauthentifizierung aus der Kombination aus Benutzername und Passwort generiert.

Bitte verzeihen Sie mir, dass ich so ausführlich und besessen von diesen kleinen Details bin. Ich möchte nur sicherstellen, dass ich das richtige Verhalten bekomme und Tests schreiben kann, die sicherstellen, dass wir keine weitere Regression bekommen.

@jvanasco , ich kann über den OAuth2-RFC berichten, bin mir jedoch nicht sicher, wie requests-oauthlib und flask-oauthlib zusammenpassen.

  1. Jawohl

Richtig.

  1. Ja, AFAIU sollte diesen Block nicht haben https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L207 -L211.

Es ist mein Verständnis, aber es wird gut sein, sich mit der Realität des Feldes zu vergleichen; dh Anfragen-oauthlib und verschiedene öffentliche Anbieter Erfahrungen. Mehrere Anfragen-oauthlib-Diskussionen https://github.com/requests/requests-oauthlib/issues/218 , https://github.com/requests/requests-oauthlib/issues/211 , https://github.com/requests /requests-oauthlib/issues/264 , bereits passiert.

Ich glaube, sie hatten eine Verwechslung zwischen client password und client secret was eigentlich zwei Formulierungen für genau dasselbe sind.
Wenn wir der Begründung hinter https://github.com/requests/requests-oauthlib/pull/206 folgen, hätte der Inhalt der PR nie so sein sollen, als würde man HTTPAuth(username, password) hinzufügen, sondern HTTPAuth(client_id, client_secret (das Passwort des Kunden).

Poke @Lukasa , @chaosct , @ibuchanan, die an den Diskussionen von Requests-oauthlib teilgenommen haben.

Groß! Vielen Dank. Ich dachte, das ist, was los ist, wollte es aber bestätigen.

Ich glaube, ich weiß jetzt, wie ich die Anfragen strukturieren möchte. Ich habe eine Handvoll Commits für das Hauptanforderungsprojekt, daher weiß ich, was die Betreuer an PRs und Funktionalität sehen möchten.

  1. Benutzername und Passwort werden nur in einer bestimmten Art von Erteilung verwendet, die sie erfordert. Wenn sie verwendet werden, dürfen sie nur im Anfragetext vorhanden sein.

Ja, zum ersten Teil: Nur eine bestimmte Art von Stipendium erfordert sie. Aber im 2. Teil über das Senden im Anfragetext sagt die Spezifikation:

Einschließen der Client-Anmeldeinformationen in den Request-Body
Die Verwendung der beiden Parameter wird NICHT EMPFOHLEN
und SOLL auf Kunden beschränkt sein, die nicht direkt nutzen können
das HTTP Basic-Authentifizierungsschema...

Aber für Server lautet es:

Der Autorisierungsserver KANN einschließlich unterstützen
die Client-Anmeldeinformationen im Request-Body...

Ein konformer Client würde die Anmeldeinformationen im Anforderungstext nicht senden.
Bei einigen teilweise implementierten Servern werden sie jedoch nur im Anforderungstext akzeptiert.
Wenn ich mich richtig erinnere, hat mein PR diese Verwirrung gelöst, indem ich den Auth-Header hinzugefügt habe,
so sendet der Client beide.

Ich glaube, @JonathanHuot hat mit dem zweiten Punkt

Hallo @ibuchanan , die Anführungszeichen, auf die Sie sich beziehen, verwenden den Begriff client credentials . Wir müssen sehr vorsichtig sein, um den Client nicht mit dem Ressourcenbesitzer (dem tatsächlichen Benutzer) zu vermischen.

Die Rollen werden hier rfc6749#1.1 anschaulich erklärt.
Dieses client credentials bezieht sich auf client_id und client_secret und der Ressourcenbesitzer bezieht sich auf username und password . Diese sind nicht austauschbar.

Das Lesen des RFC mit diesen Rollen bedeutet also, dass ein konformer Client Client-Anmeldeinformationen ( client_id , client_secret ) in HTTP Basic senden SOLLTE und Benutzeranmeldeinformationen senden MUSS ( username , password ) im Anfragetext (hier niemals eine Alternative lesen); siehe rfc6749#4.3.2 .

Einige Server lehnen die Anfrage ab (400), wenn die client_id in der Anfrage enthalten ist
Karosserie. Ich denke, die Standardeinstellung sollte die von der Spezifikation empfohlene sein.

Okay. Ich denke, die aktuelle PR für oauthlib erfüllt die oben genannten Bedenken - das include_client_id Flag erlaubt explizit, dass die client_id gesendet werden oder nicht.

In Bezug auf requests_oauthlib denke ich Folgendes:

  1. username und password erscheinen nur im Body (nicht als HTTP Basic). Wenn dieses Verhalten für eine nicht konforme Serverintegration erforderlich ist, kann der Implementierer ein auth oder headers Argument an fetch_token() senden.

  2. Die Angabe von client_id an der richtigen Stelle ist etwas nervig, aber ich denke, ich habe die Logik und die Anwendungsfälle im Griff. Dies wird definitiv eine Überprüfung benötigen.

Eine Frage, die ich an @JonathanHuot und @ibuchanan habe :

oauthlib 's OAuth2 Client und requests_oauthlib 's OAuth2Session behalten das client_secret und müssen es wiederholt aufrufen. Dies war bei OAuth1 nicht der Fall und ich denke, dies war das eigentliche Problem hinter #370. Der RFC erwähnte keine Notwendigkeit dafür, und ich konnte keine Historie finden.

Für mich ist es sinnvoll, den Client um das Speichern von client_secret und die Abhängigkeit von OAuth2Session von der Übergabe von client_secret an fetch_token und request abzulehnen.

Ich habe den PR für oauthlib (#593) leicht geändert, um die Testvariablen für Benutzername/Passwort und client_id/client_secret zu standardisieren. Ich denke, das sollte in Zukunft Verwechslungen zwischen den beiden vermeiden.

Die vorgeschlagene Änderung für request-oauthlib : https://github.com/requests/requests-oauthlib/compare/master...jvanasco :fix-oauthlib_client_id

Dieser ist etwas drastischer, denn wenn man sich den Code und die Tests ansieht, scheint es, dass die Bibliothek nur versucht hat, alle möglichen Dinge zum Laufen zu bringen, die nicht funktionieren sollten.

Der Fix bewirkt ein paar Dinge:

  1. Die Überprüfung auf username und password geschieht nur für LegacyApplicationClient Instanzen -- da dies die einzige Art sein sollte, die benötigt wird (richtig?). Es gibt einen Abschnitt unter der Überprüfung, der den Benutzernamen/das Passwort in das kwargs-Dikt einfügt. Es ist derzeit ausgerückt, da die Tests darauf hindeuten, dass andere Clients diese Informationen möglicherweise weitergeben möchten.

  2. Die Logik für die Behandlung der client_id/auth-Header wurde neu geschrieben, um sicherzustellen, dass die richtigen Authentifizierungstypen standardmäßig an der richtigen Stelle ausgeführt werden. Wenn ein Benutzer Anmeldeinformationen auf andere Weise erzwingen möchte, ist dies immer noch möglich.

  3. Frage: Gibt es eine Situation, in der client_secret nicht bestanden wird? Ich kann mir keine vorstellen, aber es gibt viele oAuth-Flows.

Also in der Requests-oAuthlib-Version:

| include_client_id | auth | Verhalten |
| ------------------- | -------------- | -------- |
| Keine (Standard) | Keine (Standard) | Erstellen Sie ein Auth-Objekt mit dem client_id . Senden Sie die client_id nicht im Body. Dies ist das Standardverhalten, da es vom RFC empfohlen wird. |
| Keine (Standard) | präsentieren | Verwenden Sie das Auth-Objekt. Rufen Sie prepare_request_body oauthlib mit include_client_id=False |
| Falsch | präsentieren | Verwenden Sie das Auth-Objekt. Rufen Sie prepare_request_body oauthlib mit include_client_id=False |
| Wahr | präsentieren | Verwenden Sie das Auth-Objekt. Rufen Sie prepare_request_body oauthlib mit include_client_id=True |
| Wahr | Keine (Standard) | Erstellen Sie ein Auth-Objekt mit dem client_id . Rufen Sie prepare_request_body oauthlib mit include_client_id=True |
| Falsch | Keine (Standard) | Erstellen Sie ein Auth-Objekt mit dem client_id . Rufen Sie prepare_request_body oauthlib mit include_client_id=False |

oder anders angegeben:

  • Auth-Objekt erstellen

    • sende die Client-ID nicht im Body:

    • (include_client_id=Keine, auth=Keine)

    • (include_client_id=False, auth=Keine)

    • senden Sie die Client-ID im Body:

    • (include_client_id=Wahr, auth=Keine)

  • ein explizites Authentifizierungsobjekt verwenden

    • sende die client_id nicht im Body:

    • (include_client_id=Keine, auth=authObject)

    • (include_client_id=False, auth=authObject)

    • sende die client_id im Body:

    • (include_client_id=Wahr, auth=authObject)

@jvanasco : sieht auf der oauthlib- und der Requests-oauthlib-Seite sehr gut aus.

Zu deiner 3. Frage:

Sie können öffentliche Clients ohne client_secret (oder leer, das ist aus Sicht des RFC) haben; Daher muss die Python-API "kein Geheimnis" unterstützen.

Der reale Anwendungsfall ist oft für native Anwendungen, bei denen Sie es vorziehen, Autorisierungscode zu verwenden, aber Sie können die Sicherheit nicht garantieren, um das Geheimnis sicher zu halten, also akzeptieren Sie entweder ein client_id ohne client_secret (oder leere client_secret die für den RFC identisch sind); oder Sie haben auch einen anderen PKCE RFC zur Verfügung (siehe https://oauth.net/2/pkce/ ). Letzteres ist jedoch noch nicht auf oauthlib Seite implementiert.

Danke. Ich werde einige Testfälle hinzufügen, um sicherzustellen, dass wir client_id ohne client_secret einreichen können. Es tut mir leid, dass ich so viele Fragen stelle, es sind einfach so viele korrekte Implementierungen dieser Spezifikation möglich (und noch mehr fehlerhafte Implementierungen, die funktionieren müssen).

@JonathanHuot die vorhandene Implementierung unterstützt nicht das Senden einer leeren Zeichenfolge für client_secret . Es wird in dieser Logik entfernt https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth2/rfc6749/parameters.py#L90 -L125 -- insbesondere Zeile 122

Um es zu unterstützen, kann man direkt nach dieser Routine so etwas hinzufügen:

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

Das würde einen leeren String für client_secret senden, wenn das Geheimnis ein leerer String ist, aber nicht client_secret wenn der Wert None .

Ich denke, es lohnt sich, dies zu unterstützen, denn wenn der RFC eine der beiden Varianten unterstützt ... wird es wahrscheinlich viele defekte Implementierungen geben, die nur eine Variante unterstützen.

Dieses ursprüngliche Problem wurde in oauthlib behoben. Das Verhalten ist jedoch immer noch da, bis https://github.com/requests/requests-oauthlib/pull/331 behoben ist.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen