sigmavirus24 / github3.py

Hi, I'm a library for interacting with GItHub's REST API in a convenient and ergonomic way. I work on Python 3.6+.
https://github3.readthedocs.io
BSD 3-Clause "New" or "Revised" License
1.21k stars 402 forks source link

UnprocessableEntity when trying to obtain an authorization token with 2FA enabled #907

Open larsrinn opened 5 years ago

larsrinn commented 5 years ago

Version Information

Please provide:

Minimum Reproducible Example

from github3 import login
import github3

def two_fa_callback():
    return input("Please enter 2FA Token: ")

auth = github3.authorize(user, password, two_factor_callback=two_fa_callback, scopes=["repo"])

this asks me for the 2FA one time password. If I enter it, I get the traceback below:

  File "/Users/xxx/.local/share/virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/api.py", line 26, in deprecation_wrapper
    return func(*args, **kwargs)
  File "/Users/xxx/.local/share/virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/api.py", line 59, in authorize
    client_secret)
  File "/Users/xxx/.local/share/virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/github.py", line 462, in authorize
    json = self._json(self._post(url, data=data), 201)
  File "/Users/xxx/.local/share/virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/models.py", line 156, in _json
    raise exceptions.error_for(response)
github3.exceptions.AuthenticationFailed: 401 Must specify two-factor authentication OTP code.

Exception information

When I'm using the same arguments to github.authorize to simply login, similarly to the approach shown here: https://github3.readthedocs.io/en/master/examples/two_factor_auth.html everything works fine. However, in that case I need to enter the 2FA Code on each request which is quite annoying.

sigmavirus24 commented 5 years ago

Thanks for reporting this! I'm surprised this is breaking. I think everything should be collaborating well enough here. I wonder if others can see anything going wrong:

Here's what's happening:

I wonder if the second request should go through our request method to re-try the challenge (in the case of a typo) or if GitHub's API returns an incorrect 401 response in the event that the username/password is wrong and the OTP is okay.


@larsrinn would you be willing to try changing

https://github.com/sigmavirus24/github3.py/blob/70828846f97427dcc48c4a9fabd4b830360c8fc2/src/github3/session.py#L122

to

return self.request(*args, **kwargs)

locally and testing out if that helps or puts you into an endless loop? (I think I avoided calling self.request a second time because I didn't want to throw people into a potential endless loop.)

larsrinn commented 5 years ago

Thanks for the answer. I changed the line locally (not against the master-branch but to what I install from PyPI. I guess that's irrelevant to answering the question here). It didn't change anything in the behaviour

sigmavirus24 commented 5 years ago
>>> import github3
i>>> import getpass
>>> twofa_cb = lambda: input("2fa token: ")
>>> gh = github3.authorize("sigmavirus24", getpass.getpass("Password: "), two_factor_callback=twofa_cb, scopes=["user"])
Password: 
2fa token: 000000
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/sigmavirus24/sandbox/github3.py/.tox/py36/lib/python3.6/site-packages/github3/api.py", line 29, in deprecation_wrapper
    return func(*args, **kwargs)
  File "/home/sigmavirus24/sandbox/github3.py/.tox/py36/lib/python3.6/site-packages/github3/api.py", line 72, in authorize
    username, password, scopes, note, note_url, client_id, client_secret
  File "/home/sigmavirus24/sandbox/github3.py/.tox/py36/lib/python3.6/site-packages/github3/github.py", line 503, in authorize
    json = self._json(self._post(url, data=data), 201)
  File "/home/sigmavirus24/sandbox/github3.py/.tox/py36/lib/python3.6/site-packages/github3/models.py", line 156, in _json
    raise exceptions.error_for(response)
github3.exceptions.UnprocessableEntity: 422 Validation Failed

I captured the response, e.g.,

try:
    github3.authorize(...)
except github3.exceptions.GitHubError as err:
    resp = err.response

And checked it:

>>> resp
<Response [422]>
>>> resp.json()
{'message': 'Validation Failed', 'errors': [{'resource': 'OauthAccess', 'code': 'missing_field', 'field': 'description'}], 'documentation_url': 'https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization'}

Part of the problem is that we're not allowing you to specify a description field for the token and with that missing this won't work. That said, I'm not getting a 401. I've no clue why you're seeing that. :confused:

larsrinn commented 5 years ago

Stupid me. I was confused while copying and pasting the stacktrace. Probably I got the trace I pasted while I was trying to debug the problem and the time delay between entering the code and sending the request outdated the OTP. Therefore the 401. The correct stacktrace would be:

  File "/Users/xxx/.virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/api.py", line 26, in deprecation_wrapper
    return func(*args, **kwargs)
  File "/Users/xxx/.virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/api.py", line 59, in authorize
    client_secret)
  File "/Users/xxx/.virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/github.py", line 462, in authorize
    json = self._json(self._post(url, data=data), 201)
  File "/Users/xxx/.virtualenvs/github-cards-4cr74AxL/lib/python3.6/site-packages/github3/models.py", line 156, in _json
    raise exceptions.error_for(response)
github3.exceptions.UnprocessableEntity: 422 Validation Failed
larsrinn commented 5 years ago

I don't know why the response says a field description is missing. According to the documentation from GitHub ( https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization ) the field note is required when posting to /authorizations. This is not enforced by the library. However, it is possible to pass an (optional) argument to authorize. Passing that along, enables the authorization to work:

auth = github3.authorize(user, password, note="this fixes it", two_factor_callback=two_fa_callback, scopes=["repo"])

So probably a call to authorize is also broken when 2FA is not enabled? Should close this issue and open a new one to require note for calls to authorizations/?

sigmavirus24 commented 5 years ago

We can probably make note required now. You're right though, I have no clue why GitHub's claiming it's called description.

Do you want to send a PR that makes note required and adds documentation about the change?

cmeiklejohn commented 5 years ago

(I deleted my previous comments, I was misunderstanding how the library worked.)