Getting the User from an APIClient() in DRF - python

I'm using a fixture in pytest that returns a client that has been logged in:
#pytest.fixture
def create_client(create_user) -> APIClient:
data = {
"email": create_user.email,
"password": "TestPassword",
}
client = APIClient()
client.post(path="/user/login/", data=data)
return client
How I get the user that has been logged in in the test?
I understand that I might be able to use a get a request from the client and get it that way:
def test_get_user(create_client):
response = create_client.get(path="/some/random/path/")
user = response.user
return user
but is there a better way to do this?

I'm sure that the create_user is another fixture and thus you can have that in your test_get_user(...) as
def test_get_user(create_client, create_user):
print(create_user)
# do some tests here

Related

How to pass arguments to a pytest fixture?

I'd like to create a session with specific user email and password, for that I need to pass these two pieces of information to a pytest fixture as arguments.
My attempt to do so:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def user_session():
def _update_credentials(email, password):
user_credentials = {
"email": email,
"password": password
}
return user_credentials
session = ApiMethods()
session.get_authorized(_update_credentials)
return session
The problem is that _update_credentials returns a function object instead of JSON, which raises TypeError: Object of type function is not JSON serializable
What sould I do to provide email and password to this fixture correctly? Help me, please :)
What it seems you're trying to do is to use the factory as fixture pattern like this:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def update_credentials():
def _update_credentials(email, password):
user_credentials = {
"email": email,
"password": password
}
return user_credentials
return _update_credentials
#fixture(scope="session")
def user_session(update_credentials):
user_credentials = update_credentials("email1", "password1")
session = ApiMethods()
session.get_authorized(user_credentials)
return session
But it seems you could just do this instead:
from pytest import fixture
from api_methods import ApiMethods
#fixture(scope="session")
def user_session():
user_credentials = {
"email": email,
"password": password
}
session = ApiMethods()
session.get_authorized(user_credentials)
return session

How to fake JWT Authorization outside of request context and get current identity?

I'm creating a Flask app with JWT Authorization and trying to test services with PyTest.
I successfully added tests to endpoints, but when I trying to add unit tests for certain function I can't access current user, because flask_jwt_extended.get_current_user() returns None.
Here is simple example:
#api.route('/listings', methods=['POST'])
#jwt_required
def create_listing():
payload = request.json
listing = listing_svc.create(payload)
return listing
def create(payload):
listing = ListingSchema().load(payload, db.session).data
class ListingSchema(ModelSchema):
id = field_for(Project, 'id', dump_only=True)
creator_user_id = field_for(Project, 'creator_user_id')
# ...
#pre_load
def set_creator_id(self, data):
current_user = flask_jwt_extended.get_current_user()
data['creator_user_id'] = current_user.id
It works when I authorize and send a request using app_context:
with client.application.app_context():
rv = client.post('/listings',
# ...
)
But what I need is to test create function without sending a request to client. In this case flask_jwt_extended.get_current_user() returns None, so I think I should set request context some way before running this function.
I tried to do this...
fake_payload = {}
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
create(fake_payload)
but still getting current_user is None
This is how I get token:
def login(email, password):
user = get_by_email(email)
if user and check_password_hash(user.password, password):
return access_token = flask_jwt_extended.create_access_token(identity=email)
If you are writing unit tests, using mock can be helpful. For jwt authorization with flask-jwt-extended you can patch the verify_jwt_in_request method which is called from the jwt_required decorator. Then you can also patch the get_jwt_identity function to return a test username. For example:
from unittest.mock import patch
#patch('path.to.some.code.get_jwt_identity')
#patch('flask_jwt_extended.view_decorators.verify_jwt_in_request')
def test_some_code(mock_jwt_required, mock_jwt_identity):
mock_jwt_identity.return_value = 'user1'
# Test jwt protected endpoint
Note: This patch is specific to latest package version flask-jwt-extended==3.21.0. The code may change with new versions.
Question from a long time ago but here is the solution for further readers.
You need to activate the app_context, then the request_context and finally call the function decorator is calling, which is verify_jwt_in_request:
fake_payload = {}
with client.application.app_context():
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
verify_jwt_in_request()
create(fake_payload)
and now you have your current_user set
If you truly want to unit test you need to unit test one function at a time. This is true test driven development in my opinion. So first write tests for create then load and so on. Use patching to mock the functionality of calls to other functions.

How to test that a view is only accessed by staff users in Django

I am learning testing in Django, and have a view which I want to test. This view should only be accessed by staff users. Suppose the view is:
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
if the request is coming from staff users, it should redirect to repositories otherwise I should get something like permission denied. I am starting with something like in tests.py.
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
# User send a request to access repositories
response = staff_users(request)
self.assertIsNone(response)
The problem is here I am not associating my request object with any users, and I also got to know about from django.contrib.admin.views.decorators import staff_member_required but not sure how to use them here. Could anyone tell me how should I test my view should only be accessed by staff users?
All you need to do is decorate your view which you want to protect as shown below:
#staff_member_required
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
If you want a custom logic for testing instead of using django decorator then you can write your own decorator as well.
def staff_users_only(function):
def wrap(request, *args, **kwargs):
profile = request.session['user_profile']
if profile is True: #then its a staff member
return function(request, *args, **kwargs)
else:
return HttpResponseRedirect('/')
wrap.__doc__=function.__doc__
wrap.__name__=function.__name__
return wrap
and use it as:
#staff_users_only
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
Edit
Association of sessions on request object for testing can be done as:
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
#create a session which will hold the user profile that will be used in by our custom decorator
request.session = {} #Session middleware is not available in UnitTest hence create a blank dictionary for testing purpose
request.session['user_profile'] = self.user.is_staff #assuming its django user.
# User send a request to access repositories
response = staff_users(request)
#Check the response type for appropriate action
self.assertIsNone(response)
Edit 2
Also it would be a far better idea to use django Client library for testing:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'abc', 'password': '1234'})
>>> response.status_code
200
>>> response = c.get('/user/protected-page/')
>>> response.content
b'<!DOCTYPE html...

How do you implement token authentication in Flask?

I'm trying to allow users to login to my Flask app using their accounts from a separate web service. I can contact the api of this web service and receive a security token. How do I use this token to authenticate users so that they have access to restricted views?
I don't need to save users into my own database. I only want to authenticate them for a session. I believe this can be done using Flask-Security and the #auth_token_required decorator but the documentation is not very detailed and I'm not sure how to implement this.
EDIT:
Here's a code example:
#main.route("/login", methods=["GET", "POST"])
def login():
payload = {"User": "john", "Password": "password123"}
url = "http://webserviceexample/api/login"
headers = {'content-type': 'application/json'})
#login to web service
r = requests.post(url, headers=headers, json=payload)
response = r.json()
if (r.status_code is 200):
token = response['user']['authentication_token']
# allow user into protected view
return render_template("login.html", form=form)
#main.route('/protected')
#auth_token_required
def protected():
return render_template('protected.html')
Hey there Amedrikaner!
It looks like your use-case is simple enough that we can implement this ourselves. In the code below, I'll be storing your token in the users session and checking in a new wrapper. Let's get started by making our own wrapper, I usually just put these in a wrappers.py file but can you can place it where you like.
def require_api_token(func):
#wraps(func)
def check_token(*args, **kwargs):
# Check to see if it's in their session
if 'api_session_token' not in session:
# If it isn't return our access denied message (you can also return a redirect or render_template)
return Response("Access denied")
# Otherwise just send them where they wanted to go
return func(*args, **kwargs)
return check_token
Cool!
Now we've got our wrapper implemented we can just save their token to the session. Super simple. Let's modify your function...
#main.route("/login", methods=["GET", "POST"])
def login():
payload = {"User": "john", "Password": "password123"}
url = "http://webserviceexample/api/login"
headers = {'content-type': 'application/json'})
#login to web service
r = requests.post(url, headers=headers, json=payload)
response = r.json()
if (r.status_code is 200):
token = response['user']['authentication_token']
# Move the import to the top of your file!
from flask import session
# Put it in the session
session['api_session_token'] = token
# allow user into protected view
return render_template("login.html", form=form)
Now you can check the protected views using the #require_api_token wrapper, like this...
#main.route('/super_secret')
#require_api_token
def super_secret():
return "Sssshhh, this is a secret"
EDIT
Woah! I forgot to mention you need to set your SECRET_KEY in your apps config.
Just a config.py file with SECRET_KEY="SOME_RANDOM_STRING" will do. Then load it with...
main.config.from_object(config)

What's the proper way to test token-based auth using APIRequestFactory?

The query to my endpoint works fine (as long as I pass it a valid token), it returns the json representation of my response data.
The code in the service api that calls my endpoint, passing an auth token in the header:
headers = {'content-type': 'application/json',
'Authorization': 'Token {}'.format(myToken)}
url = 'http://localhost:8000/my_endpoint/'
r = session.get(url=url, params=params, headers=headers)
In views.py, I have a method decorator that wraps the dispatch method on the view (viewsets.ReadOnlyModelViewSet):
def login_required(f):
def check_login_and_call(request, *args, **kwargs):
authentication = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(authentication, str):
authentication = authentication.encode(HTTP_HEADER_ENCODING)
key = authentication.split()
if not key or len(key) != 2:
raise PermissionDenied('Authentication failed.')
user, token = authenticate_credentials(key[1])
return f(request, *args, **kwargs)
return check_login_and_call
I'm trying to write a test to authenticate the request using a token:
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory
from rest_framework.test import APITestCase
from rest_framework.test import force_authenticate
class EndpointViewTest(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = User.objects.create_user(
username='user#foo.com', email='user#foo.com', password='top_secret')
self.token = Token.objects.create(user=self.user)
self.token.save()
def test_token_auth(self):
request = self.factory.get('/my_endpoint')
force_authenticate(request, token=self.token.key)
view = views.EndpointViewSet.as_view({'get': 'list'})
response = view(request)
self.assertEqual(response.status_code, 200)
json_response = json.loads(response.render().content)['results']
For some reason, I cannot get the request to properly pass the token for this test. Using force_authenticate doesn't seem to change the header that I'm using for validating the token. The current output is raising "PermissionDenied: Authentication failed." because the token isn't being set on the request.
Is there a proper way to set this in the request header in my test or to refactor the way I'm using it in the first place?
I found a way to get the test to pass, but please post if you have a better idea of how to handle any of this.
request = self.factory.get('/my_endpoint', HTTP_AUTHORIZATION='Token {}'.format(self.token))
force_authenticate(request, user=self.user)
After changing the above two lines of the test, it seems to authenticate based on the token properly.
I wanted to test the authentication function itself, so forcing authentication wans't an option.
One way to properly pass the token is to use APIClient, which you already have imported.
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)
response = client.get('/api/vehicles/')
That sets your given token into the request header and lets the back end decide if it's valid or not.
Sorry for digging this old thread up, but if someone is using APIClient() to do their tests you can do the following:
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
class VehicleCreationTests(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_superuser('admin', 'admin#admin.com', 'admin123')
self.token = Token.objects.create(user=self.user)
def testcase(self):
self.client.force_login(user=self.user)
response = self.client.post('/api/vehicles/', data=vehicle_data, format='json', HTTP_AUTHORIZATION=self.token)
self.assertEqual(response.status_code, 201)
Really good resource that I've used to come up with this is django-rest-framework-jwt tests
The simpler way to force_authentication using a built-in method from APITestCase is:
class Test(APITestCase):
def setUp(self):
user1 = User.objects.create_user(username='foo')
self.client.force_authenticate(user=user1) # self.client is from APITestCase
... the rest of your tests ...

Categories

Resources