django drf testing social oauth2 with google - django

I'm trying to test drf-social-oauth2's integration with Google via python manage.py test with the following test class:
class DRFSocialOAuthIntegrationTest(TestCase):
def setUp(self):
self.test_user = UserModel.objects.create_user("test_user", "test#user.com", TEST_USER_PASSWORD)
self.application = Application(
name="Test Application",
redirect_uris="",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_PASSWORD, # https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/models.py
)
self.application.save()
# Every test needs a client.
self.client = Client()
def tearDown(self):
self.application.delete()
self.test_user.delete()
def test_drf_social_oauth2_integration(self):
'''Following testing steps found at curl -X POST -d "grant_type=convert_token&client_id=<django-oauth-generated-client_id>&client_secret=<django-oauth-generated-client_secret>&backend=google-oauth2&token=<google_token>" http://localhost:8000/auth/convert-token'''
def convert_google_token(self):
'''
Convert Google token to our access token
curl -X POST -d "grant_type=convert_token&client_id=<client_id>&client_secret=<client_secret>&backend=google-oauth2&token=<google_token>" http://localhost:8000/auth/convert-token
'''
return self.client.post(
'/auth/convert-token',
{
'grant_type': 'convert_token',
'client_id': self.application.client_id,
'client_secret': self.application.client_secret,
'backend': 'google-oauth2',
'token': <google_token>
}
)
That seems to work fine, but I have to keep feeding it the google_token by manually navigating to https://developers.google.com/oauthplayground/?code=<my_code>&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid&authuser=0&prompt=consent#:
Once there, I click on the 'Exchange authorization code for tokens' button, get the access token, and paste that access token into the tokenparameter of the request inside convert_google_token(). This doesn't feel very automated. I'm not sure if I should just click the checkbox so that the access token is refreshed automatically and therefore never have to edit it in convert_google_token(). Or maybe I'm supposed to get that access token programmatically. But I believe that would entail getting the authorization code first, which would have to also be programmatically since it is a one-time code, right?
So in order to get that authorization code, I was trying to parse the html response from developers.google.com/oauthplayground/ like so:
def get_google_authorization_code_html():
import requests
return requests.post(
'https://developers.google.com/oauthplayground/',
json={
'code': <my_code>,
'scope': "email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid",
'authuser': "0",
'prompt': "consent"
}
)
response = get_google_authorization_code_html() #;print(f'Google authorization code html returned: {response.content=}')
self.assertEqual(response.status_code, 200)
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content) ;print('Google authorization code html return: '); print(soup.prettify())
However, the input element that in the browser shows the authorization code is actually blank in the html response, so I can't retrieve it from there. Would this even be the proper way to go about this? I may be missing key knowledge as I'm pretty new to OAuth2, Django, and testing. I've been trying to read up on it and particularly diagrams such as the one found in this Medium article make me think I'm more or less on the right track, but I'm not sure.

You are making a trivial mistake there. Whenever you need the response of a third-party API for testing purposes, you have to mock the response of the API and also/perhaps mock some of your code as well. In this case, you must mock the drf convert-token, otherwise, you will have to keep reading google's token (and this is not ideal).

Related

Manually validate flask-extended-jwt's access token

I have a SPA app that contains an form with an upload file field. I have a rest API whose endpoints are protected via flask-extended-jwt JWT. To authenticate the REST endpoints I use #jwt_required. I want to authenticate the upload request as well.
Because of the client side I can't add an Authorization Bearer header so I thought to add the access token as a hidden field when submitting the form.
What is the best way to manually validate the JWT access token after I read it from the form?
class Upload(Resource):
def post(self):
#TODO: check for access token
access_token = None
if 'access_token' in request.form and request.form['access_token']:
access_token = request.form['access_token']
else:
message = json.dumps({'message': 'Invalid or missing token', 'success': False})
return Response(response=message, status=401, mimetype='text/plain')
if access_token:
#TODO: validate_token(access_token)
Thank you
Author of flask-jwt-extended here. That's a great question. There is currently no supported way to do that in the extension, the grabbing the token from the request and decoding it are tightly coupled together. This would be hard to de-couple because there is a lot of conditional things that are going on when the full decode chain runs. For example, checking the CSRF value only if the request is sent in via a cookie, or differentiating between an access and refresh token for the sake of the blacklisting feature.
A generalized function could be created, it's signature would look something like decode_and_verify_jwt(encoded_token, is_access_token=True, check_csrf=False). However, this would complicate the rest of the code in flask_jwt_extended and be a rather confusing function to use for the general case.
I think in this case it would be easier just to add a fourth lookup in the extension, so you could use something like:
app.config['JWT_TOKEN_LOCATION'] = ['headers', 'forms']
app.config['JWT_FORM_KEY'] = 'access_token'
# Use the rest of the application normally
If you want to make a ticket on the github page so I can track this, I would be happy to work on it.

Fetching verification code and request_token parameters from paypal redirect url

I am currently trying to understand Paypal APIs by writing a flask app which consumes various APIs. What i have bugun with is interacting with Permission services for classic APIs. I have a sandbox account which provides me with the necessities to make my requests. In one of my view functions i have succefully accessed the request permission page and received a the paypal's redirect to the callback Url i provided in my request. This is my code(note that i am still 'young' in python so suggestions for improvement are welcomed)
#account.route('/grant_auth')
def get_request_token():
"""handle the proccess of getting the request token"""
if request.method == 'GET':
url = "https://svcs.sandbox.paypal.com/Permissions/RequestPermissions"
headers = {
'X-PAYPAL-SECURITY-USERID': '**********',
'X-PAYPAL-SECURITY-PASSWORD': '*********',
'X-PAYPAL-SECURITY-SIGNATURE': '************',
'X-PAYPAL-REQUEST-DATA-FORMAT': 'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT': 'JSON',
'X-PAYPAL-APPLICATION-ID': 'APP-80W284485P519543T'
}
payload = {
'scope': 'ACCOUNT_BALANCE',
'callback': 'http://127.0.0.1:5000/access_token',
'request_Envelop': {
'errorLanguage': 'en_US'
}
}
res_details = requests.post(
url, data=json.dumps(payload), headers=headers)
res_json_format = res_details.json()
if str(res_json_format['responseEnvelope']['ack']) == 'Failure':
return render_template('account.html', response='something went wrong with the application')
if str(res_json_format['responseEnvelope']['ack']) == 'Success':
token = res_json_format['token']
pal_permission_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_grant-permission&request_token={}'.format(token)
return redirect(pal_permission_url)
else:
return render_template('account.html', response='something went wrong with the application')
Thats what i have done:
When i run that code and i visit /grant_auth
I am redirected to the paypal page where i can grant the permission when i grant i am redirected to http://127.0.0.1:5000/access_token?request_token=somevalue&verification_code=somevalue with an additonal parameters appended to it as such.
Please help me how i can obtain the verifiaction_code and request_token from the url(or even better what is the best direction to go from here) since i need the two values to make another request to the GetAccesToken API so that i can make requests on behalf of users who have granted the permission.
Retrieving parameters from a URL
seems to slightly address my problem but not fully because the url being parsed there is already existing unlike my case where the url changes. Thanks in Advance

Unit testing Django with remote calls to another server

So I have a Django app, which as part of its functionality makes a request (using the requests module) to another server. What I want to do is have a server available for unittesting which gives me canned responses to test requests from the Django app (allowing to test how Django handles the different potential responses).
An example of the code would be:
payload = {'access_key': key,
'username': name}
response = requests.get(downstream_url, params=payload)
# Handle response here ...
I've read that you can use SimpleHTTPServer to accomplish this, but I'm not sure of how I use it to this end, any thoughts would be much appreciated!
Use the mock module.
from mock import patch, MagicMock
#patch('your.module.requests')
def test_something(self, requests_mock):
response = MagicMock()
response.json.return_value = {'key': 'value'}
requests_mock.get.return_value = response
…
requests_mock.get.assert_called_once_with(…)
response.json.assert_called_once()
Much more examples in the docs.
You don't need to (and should not) test the code that makes the request. You want to mock out that part and focus on testing the logic that handles the response.

Unit test through an oauth flow in Django

I have an oauth flow in which a user grants access to a certain scope and then my application can do stuff. For this to work, I need an access token with the defined scope.
I implemented this (with the django allauth package) and it works great. But...
I would like to test it.
This is what I have so far (request package is like an urllib on steroids):
login = self.client.login(username='test_user', password='test')
self.assertTrue(login)
resp = self.client.post(reverse('oauth_login'))
self.assertEqual(resp.status_code, 302)
payload = {'session_key': 'email', 'session_password': 'pw', }
resp2 = requests.post(resp['location'], data=payload)
resp3 = self.client.get(reverse('do_stuff_with_access_token'))
self.assertEqual(resp.status_code, 302)
The issue here is that I do not get the access token in my request variables. My guess is that I am going out of the scope of the application and Django does not get the variable in its request scope.
How can you test this in an elegant manner? Mocking an access_token seems a bit wrong to me. I am now trying to go Selenium for filling in the form, but even that is not really a success so far...
Thanks!
Kudos to Mark!
To help anyone on their way.
Mocking worked out. In my case (django 1.4) you need to add your tokens to the session. A lot of different advices can be found on the net, but I like simple things and this works in Django at least with the test suite:
session = self.client.session
session['request_token'] = {...}
session['access_token'] = {...}
session.save()
I faced the same problem and found the following solved my dilemma:
user = User.objects.get(username='lauren')
client = APIClient()
client.force_authenticate(user=user)
This was taken directly from the Django documentation:
http://www.django-rest-framework.org/api-guide/testing/#force_authenticateusernone-tokennone
Unfortunately it took a number of searches before getting to this point. I hope this saves someone else some time.

Setting HTTP_REFERER header in Django test

I'm working on a Django web application which (amongst other things) needs to handle transaction status info sent using a POST request.
In addition to the HTTP security supported by the payment gateway, my view checks request.META['HTTP_REFERER'] against an entry in settings.py to try to prevent funny business:
if request.META.get('HTTP_REFERER', '') != settings.PAYMENT_URL and not settings.DEBUG:
return HttpResponseForbidden('Incorrect source URL for updating payment status')
Now I'd like to work out how to test this behaviour.
I can generate a failure easily enough; HTTP_REFERER is (predictably) None with a normal page load:
def test_transaction_status_succeeds(self):
response = self.client.post(reverse('transaction_status'), { ... })
self.assertEqual(response.status_code, 403)
How, though, can I fake a successful submission? I've tried setting HTTP_REFERER in extra, e.g. self.client.post(..., extra={'HTTP_REFERER': 'http://foo/bar'}), but this isn't working; the view is apparently still seeing a blank header.
Does the test client even support custom headers? Is there a work-around if not? I'm using Django 1.1, and would prefer not to upgrade just yet if at all possible.
Almost right. It's actually:
def transaction_status_suceeds(self):
response = self.client.post(reverse('transaction_status'), {}, HTTP_REFERER='http://foo/bar')
I'd missed a ** (scatter operator / keyword argument unpacking operator / whatever) when reading the source of test/client.py; extra ends up being a dictionary of extra keyword arguments to the function itself.
You can pass HTTP headers to the constructor of Client:
from django.test import Client
from django.urls import reverse
client = Client(
HTTP_USER_AGENT='Mozilla/5.0',
HTTP_REFERER='http://www.google.com',
)
response1 = client.get(reverse('foo'))
response2 = client.get(reverse('bar'))
This way you don't need to pass headers every time you make a request.