8000 Provide TokenAuth class to requests to ensure explicit usage by electrofelix · Pull Request #800 · sigmavirus24/github3.py · GitHub
[go: up one dir, main page]

Skip to content

Provide TokenAuth class to requests to ensure explicit usage #800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

electrofelix
Copy link
Contributor

Requests will default to reading .netrc files when auth is set to
'None', therefore it's necessary to provide a callable that returns
what is given with nothing extra set as the value for auth to ensure it
will use the token given rather than reading from a .netrc file.

To reproduce, add a token for one user to ~/.netrc and attempt
to authenticate with a token for another user using this library and
calling me() on the returned object will describe the user in ~/.netrc
rather than the user of the token used with this library.

@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from 9ed058f to 7f6dd1c Compare March 20, 2018 14:33
@electrofelix
Copy link
Contributor Author

To test this requires performing real requests to the Github API with multiple users and it's not clear that this is feasible?

You can also reproduce this by creating two tokens with different scopes, the first added to .netrc should only have access to 'public_repo' and nothing else, and the second should have access to 'read:user'. If you insert the first token into .netrc and use the second token with the following you'll get an exception rather than a successful result.

.netrc

machine api.github.com
login <user>
password <token1>
GITHUB_TOKEN="<token2>" python -c 'import logging
import os

import github3 as github


logging.basicConfig(level=logging.DEBUG)
g_admin = github.login(token=os.environ["GITHUB_TOKEN"])
print(g_admin.me())'

Without this change the output is:

INFO:github3:Building a url from ('https://api.github.com', 'user')
INFO:github3:Missed the cache building the url
DEBUG:github3:GET https://api.github.com/user with {}
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.github.com
DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /user HTTP/1.1" 200 None
INFO:github3:JSON was returned
Traceback (most recent call last):
  File "<string>", line 9, in <module>
  File "github3/decorators.py", line 30, in auth_wrapper
    return func(self, *args, **kwargs)
  File "github3/github.py", line 1019, in me
    return self._instance_or_null(users.AuthenticatedUser, json)
  File "github3/models.py", line 146, in _instance_or_null
    return instance_class(json, self)
  File "github3/models.py", line 50, in __init__
    raise exceptions.IncompleteResponse(json, kerr)
github3.exceptions.IncompleteResponse: None The library was expecting more data in the response (KeyError(u'disk_usage',)). Either GitHub modified it's response body, or your token is not properly scoped to retrieve this information.

With this change it works fine because it uses the token with the scope that can read the correct user information.

@omgjlk omgjlk added the Bug label Mar 20, 2018
@omgjlk
Copy link
Collaborator
omgjlk commented Mar 20, 2018

Nice catch on this bug. As for testing, would it simply be enough to create a .netrc file in the test with an invalid token in it, to ensure that the test using correct credentials doesn't fail?

@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from 7f6dd1c to bc2a0ab Compare March 20, 2018 20:05
Copy link
Contributor Author
@electrofelix electrofelix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@omgjlk I've not used pytest before and I'm not sure how to correctly use the tmpdir fixture to get an isolated test to use a .netrc file. Maybe you might have some suggestions on what I need to look at to get something along these lines working?

"password invalid_token_for_test_verification"
)

with mock.patch.dict('os.environ', {'HOME': tmpdir}):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to test this behaviour I think I need the above tmpdir fixture, but enabling that seems to cause this class to lose it's self.gh attribute resulting in an error being thrown on self.token_login() above.

@@ -19,3 +21,27 @@ def _two_factor_auth():
match_requests_on=match):
r = self.session.get('https://api.github.com/users/sigmavirus24')
assert r.status_code == 200

@pytest.fixture(autouse=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct way to use this? I'm not familar with pytest but this doesn't seem to use it for only this test and has some side effects in breaking the other test in this class.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. @pytest.fixture declares the function as a fixture. autouse=True tells it to run before every test function. It seems like you wanted a temporary directory fixture?

self.auth = None
# Requests requires a callable for 'auth' otherwise it
# will attempt to use any auth in .netrc
self.auth = lambda x: x
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do this or we could set trust_env = False. Setting a "null auth" might affect our decorators that introspect the auth on a session.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting trust_env = False breaks use of http_proxy/https_proxy/no_proxy env settings, and would require then explicit handling of proxy env vars from this library down to python requests.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make more sense to to treat our authentication better.

Should we have a proper auth handler for this, e.g.,

class TokenAuth(requests.auth.AuthBase):
    def __init__(self, token):
        self.token = token

    def __call__(self, request):
        request.headers['Authorization'] = 'token {}'.format(token)

That could negate this entirely.

@@ -19,3 +21,27 @@ def _two_factor_auth():
match_requests_on=match):
r = self.session.get('https://api.github.com/users/sigmavirus24')
assert r.status_code == 200

@pytest.fixture(autouse=True)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. @pytest.fixture declares the function as a fixture. autouse=True tells it to run before every test function. It seems like you wanted a temporary directory fixture?

@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from bc2a0ab to c5e102a Compare March 21, 2018 09:59
@electrofelix
Copy link
Contributor Author

So I've got the tmpdir fixture working, but it seems that something in the test framework doesn't like proxies so I'm not able to record the responses correctly and test that I can inspect the auth token sent to confirm it's the one set rather than the one coming from the .netrc file.

@electrofelix
Copy link
Contributor Author

Never mind, just realised I forgot I needed to set:

TOX_TESTENV_PASSENV="http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY" tox -e py27

in order to test with proxies ... doh!

@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from c5e102a to a9ce2e5 Compare March 21, 2018 11:15
@electrofelix
Copy link
Contributor Author

And in better news, it seems this can be tested via a unit test by use of prepare_request which triggers the reading of .netrc/_netrc files when auth is set to None.

Undoing the change in github3/session.py will trigger a test failure without needing to record/replay a real request.

@@ -156,6 +160,32 @@ def test_token_auth_does_not_use_falsey_values(self):
s.token_auth(token)
assert 'Authorization' not in s.headers

def test_token_auth_with_netrc_works(self):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why you're setting up self.tmpdir for the whole class when we only need it for this test.

Here's the documentation https://docs.pytest.org/en/latest/tmpdir.html

You don't need to import pytest, you can do

def test_token_auth_with_netrc_works(self, tmpdir):
    """Verify the behaviour of the library when a token is provided and netrc is present."""

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason that didn't work in the integration tests and I just copied across what did when moving it to a unit test, but seems to work fine for the unit tests.

self.auth = None
# Requests requires a callable for 'auth' otherwise it
# will attempt to use any auth in .netrc
self.auth = lambda x: x
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make more sense to to treat our authentication better.

Should we have a proper auth handler for this, e.g.,

class TokenAuth(requests.auth.AuthBase):
    def __init__(self, token):
        self.token = token

    def __call__(self, request):
        request.headers['Authorization'] = 'token {}'.format(token)

That could negate this entirely.

@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from a9ce2e5 to d829162 Compare March 21, 2018 12:40
@electrofelix electrofelix changed the title Prevent use of .netrc files when setting token auth Provide TokenAuth class to requests to ensure explicit usage Mar 21, 2018
assert 'Authorization' not in pr.headers

def test_token_auth_with_netrc_works(self, tmpdir):

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most tests in this file have a docstring, please add one here. It doesn't need to be one-line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, will update

@@ -146,15 +151,43 @@ def test_token_auth_disables_basic_auth(self):
s = self.build_session()
s.auth = ('foo', 'bar')
s.token_auth('token goes here')
assert s.auth is None
assert s.auth != ('foo', 'bar')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about another assertion, e.g.,

assert isinstance(s.auth, github3.session.TokenAuth)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, or did you want me to replace the existing assertion?

Provide a class based on BaseAuth that requests can all to set the
required headers on any calls made to ensure that it only uses the
given auth scheme.

In the event that `auth` is set to None, requests will default to
reading .netrc/_netrc files for any credentials set by the user. When
given an auth token or password it's necessary to ensure that it will
ignore any .netrc settings.
@electrofelix electrofelix force-pushed the token-auth-disables-netrc branch from d829162 to 65eb7a2 Compare March 21, 2018 19:13
@sigmavirus24 sigmavirus24 merged commit aa50e04 into sigmavirus24:develop Mar 22, 2018
@electrofelix electrofelix deleted the token-auth-disables-netrc branch March 22, 2018 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0