-
Notifications
You must be signed in to change notification settings - Fork 406
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
Provide TokenAuth class to requests to ensure explicit usage #800
Conversation
9ed058f
to
7f6dd1c
Compare
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
Without this change the output is:
With this change it works fine because it uses the token with the scope that can read the correct user information. |
Nice catch on this bug. As for testing, would it simply be enough to create a |
7f6dd1c
to
bc2a0ab
Compare
There was a problem hiding this 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?
tests/integration/test_session.py
Outdated
"password invalid_token_for_test_verification" | ||
) | ||
|
||
with mock.patch.dict('os.environ', {'HOME': tmpdir}): |
There was a problem hiding this comment.
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.
tests/integration/test_session.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
github3/session.py
Outdated
self.auth = None | ||
# Requests requires a callable for 'auth' otherwise it | ||
# will attempt to use any auth in .netrc | ||
self.auth = lambda x: x |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
tests/integration/test_session.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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?
bc2a0ab
to
c5e102a
Compare
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. |
Never mind, just realised I forgot I needed to set:
in order to test with proxies ... doh! |
c5e102a
to
a9ce2e5
Compare
And in better news, it seems this can be tested via a unit test by use of Undoing the change in github3/session.py will trigger a test failure without needing to record/replay a real request. |
tests/unit/test_github_session.py
Outdated
@@ -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): |
There was a problem hiding this comment.
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."""
There was a problem hiding this comment.
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.
github3/session.py
Outdated
self.auth = None | ||
# Requests requires a callable for 'auth' otherwise it | ||
# will attempt to use any auth in .netrc | ||
self.auth = lambda x: x |
There was a problem hiding this comment.
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.
a9ce2e5
to
d829162
Compare
tests/unit/test_github_session.py
Outdated
assert 'Authorization' not in pr.headers | ||
|
||
def test_token_auth_with_netrc_works(self, tmpdir): | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
d829162
to
65eb7a2
Compare
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 ~/.netrcrather than the user of the token used with this library.