github3.py icon indicating copy to clipboard operation
github3.py copied to clipboard

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

Open larsrinn opened this issue 6 years ago • 7 comments

Version Information

Please provide:

  • The version of Python you're using 3.6.5

  • The version of pip you used to install github3.py pipenv version 2018.7.1 and pip 18.0

  • The version of github3.py, requests, uritemplate, and dateutil installed

    • github3.py: 1.2.0
    • uritemplate: 3.0.0
    • requests: 2.20.1
    • dateutil: 2.7.5

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.

larsrinn avatar Nov 22 '18 08:11 larsrinn

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.)

sigmavirus24 avatar Nov 22 '18 13:11 sigmavirus24

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

larsrinn avatar Nov 23 '18 20:11 larsrinn

>>> 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:

sigmavirus24 avatar Nov 24 '18 01:11 sigmavirus24

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 avatar Nov 24 '18 10:11 larsrinn

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/?

larsrinn avatar Nov 24 '18 10:11 larsrinn

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?

sigmavirus24 avatar Nov 24 '18 13:11 sigmavirus24

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

cmeiklejohn avatar Aug 25 '19 14:08 cmeiklejohn