Assuming that I have an API endpoint, whose resources are accessible to authorised users only who possess a valid access token, similar with this:
from flask_restful import Resource
from flask_jwt_extended import jwt_required
class Collection(Resource):
#jwt_required
def get(self):
"""
GET response implementation here.
"""
# Queries and logic here
data = 10
if(is_everythig_ok()):
return {"data": data}, 200
else:
return {"message":"Failed to get data."}, 400
And assuming that there is a LoginUser endpoint which returns a valid access_token, how can I write some unit tests to reproduce the two status codes (200 for success and 400 for failure) while user HAS a valid access token AND also the case when the user DOES NOT have a valid access_token.
I have test my endpoints with POSTMAN and it seems ok, but I also need to write some unit tests for proof. So, what is the proper way of doing that?
Since this is an API, what you really want are integration tests. The way I do this, is like this:
create a test user
request a valid token
access a protected resource with the valid token
access the resource with an invalid token
any other tests making sure you cover every method in every controller.
remove the test user
You will end up with a lot of integration tests which you can automate, postman is great at this, you can build collections for every endpoint and run them easily.
More than this, you can start measuring how long each request takes to execute and can start looking at those which take too long.
Unit test the logic inside your methods, but not your authorization system, not your endpoints and not your controllers. Those you integration test.
Related
I am running a django app with a REST API and I am protecting my API endpoint with a custom permission. It looks like this:
class MyPermission(permissions.BasePermission):
def has_permission(self, request, view):
host = request.META.get('HTTP_HOST', None)
return host == "myhost.de"
)
The idea is that my API is only accessible via "myhost.de".
Right now I am testing this with pytest. I can set my headers with:
#pytest.fixture()
def request_unauth(self, client):
result = client.get(
"myurl",
headers={'HTTP_HOST', 'myhost.de'},
content_type="application/json",
)
return result
def test_host(request_unauth):
assert request_unauth.status_code == 200
Since I can easily fake my headers I assume that this might also be easily done with other tools? How can MyPermission be evaluated from a security perspective?
Thanks so much for any help and hints. Very much appreciated.
Checking the Host header like that does not make sense, and will not protect against 3rd party clients as you described in comments. An attacker can create an arbitrary client and send request to your API, and that request can (and will) include the correct Host header as any other legitimate request.
Also based on your comments, you want to authenticate the client application, which is not technically possible, as it has been discussed many times. With some work (the amount of which you can influence somewhat) anybody can create a different client to your API, and there is no secure way you could prevent that, because anything you include in your client will be known to users (attackers), and will allow them to copy it. You can and probably should authenticate your users though, check access patterns, implement rate limiting, revoke user access in case of suspicious activity and so on - but this is all based on user authentication.
You can also prevent access from a client running in a standard browser on different domain, by sending the correct CORS headers (or not sending CORS headers at all) in your API.
I am building a RESTful API for an upcoming project. This needs some kind of user account verification. I implemented a token based confirmation procedure previously using itsdangerous. But I wonder wether I can accomplish the same by using JWT-Extended, as its already part of my app and I want to keep the number of dependencies as low as possible.
Could I just use a normal access_token for that?
I appreciate your help!
Edit:
I implemented the following two methods and they seem to work. I am just not sure, if this is considered good practice.
#blueprint.route('/gen_confirmation_token', methods=['GET'])
#jwt_required
def gen_confirmation_token():
access_token = create_access_token(identity=get_jwt_identity(), user_claims={"confirm": True}, expires_delta=dt.timedelta(seconds=3600))
# TODO send a link to mail
return jsonify({"message": "confirmation token sent"}), 200
#blueprint.route('/confirm/<string:token>', methods=['GET'])
#jwt_required
def confirm_user(token):
user_identity = get_jwt_identity()
current_user = User.query.get(user_identity)
decoded_token = decode_token(token)
if decoded_token['identity'] == user_identity and decoded_token['user_claims'].get('confirm', False):
current_user.confirm()
return jsonify({"message": "user confirmed"}), 200
return jsonify({"message": "invalid confirmation token"}), 400
EDIT
Seeing the code you've added, which seems to be working, the nature of my answer changes. I think your solution to the problem would be considered good practice. The main problems you have are security, i.e. no one should be able to create their own token, which the hash value confirms, and the tokens should be personalised in such a way that one and only one person can use them, which the user identity guarantees.
Since you can encode as much information as you want in a JWT token, you should be able to store the information you need in it as well. I'm not sure what format you were thinking of, but if you were to, for example, store the confirmation step someone still has to reach, you can store something like the following:
#jwt.user_claims_loader
def add_confirmation_status_to_token(user):
"""Given an identity, add the confirmation status to the token"""
return dict(status=user.confirmed_email)
For more information, have a look here
If I have a server end point that say does a simple task like initializes an API with a token that I generate client side, and then print the users account info.
Can I initialize() the API globally so the user can do other tasks after printing account info?
and
How does that affect other users initializing() and printing info if they do it at the same time?
I don't understand how this server works and any help would be great. Thank you!
If I'm understanding you correctly, it sounds like the easiest way to accomplish what you're trying to do is to use the flask-login module.
You will want to create an endpoint / Flask route (for example, '/login') that the user will send a POST request to with a username (and usually also a password). When the login is successful, the user's browser will have a cookie set with a token that will allow them to access Flask routes that have a #login_required decorator attached.
In your Python code for those routes, you will be able to access the "current_user", which will allow you to tailor your response to the particular user sending the request.
This way of doing things will allow you to deal with your first question, which seems to be about saving a user's identity across requests, and it will also allow you to deal with your second question, which seems to be about the problem of having different users accessing the same endpoint while expecting different responses.
Nathan's answer points you in the right direction, but I'll make a comment about state and scope because you asked about multiple users.
Firstly, the normal way to do this would be for the server to generate the random token and send it back to the user.
client request 1 > init > create token > store along with init data > return token to user
client request 2 > something(token) > find data related to token > return data to user
When you run Flask (and most webapps) you try to make it so that each request is independent. In your case, the user calls an endpoint and identifies themselves by passing a token. If you make a record of that token somewhere (normally in a database) then you can identify that user's data by looking up the same token they pass on subsequent requests.
Where you choose to store that information is important. As I say a database is the normal recommended approach as it's built to be accessed safely by multiple people at the same time.
You might be tempted to not do the database thing and actually store the token / data information in a global variable inside python. Here's the reason why that's (probably) not going to work:
Flask is a wsgi server, and how it behaves when up and running depends on how it's configured. I generally use uwsgi with several different processes. Each process will have its own state that can't see one another. In this (standard / common) configuration, you don't know which process is going to receive any given request.
request 1 > init(token) > process 1 > store token in global vars
request 2 > other(token) > process 2 > can't find token in global vars
That's why we use a database to store all shared information:
request 1 > init(token) > process 1 > store token in db
request 2 > other(token) > process 2 > can find token db
I'm currently having a hard time getting some of my Django tests to work.
What I'm trying to do is test if a given URL (the REST API I want to consume) is up and running (returning status code 200) and later on if it's responding the expected values.
However, all I get returned is a status code 404 (Page not found), even though the URL is definitely the right one. (Tried the exact string in my browser)
This is the code:
from django.test import TestCase
class RestTest(TestCase):
def test_api_test_endpoint(self):
response = self.client.get("http://ip.to.my.api:8181/test/")
self.assertEqual(response.status_code, 200, "Status code not equals 200")
It always returns a 404 instead of a 200...
Anyone knows what I do wrong here?
self.client is not a real HTTP client; it's the Django test client, which simulates requests to your own app for the purposes of testing. It doesn't make HTTP requests, and it only accepts a path, not a full URL.
If you really needed to check that an external URL was up, you would need a proper HTTP client like requests. However this doesn't seem to be an appropriate thing to do in a test case, since it depends on an external API; if that API went down, suddenly your tests would fail, which seems odd. This could be something that you do in a monitoring job, but not in a test.
I have to write some tests for some services I build that connect our backend to a mobile app another team member is building. I was asked to write some unit tests once I finished them. I am not familiar with Django testing so I wanted to ask if someone could give me an example of how you would test one of the services. That way I can then learn by example and do the rest on my own?
This is one example of a service I built that finds if there is a user by that email in our database and return a json object:
#csrf_exempt
def user_find(request):
args = json.loads(request.body, object_hook=utils._datetime_decoder)
providedEmail = args['providedEmail']
try:
user = User.objects.get(email=providedEmail)
user_dict = {'exists': 'true', 'name': user.first_name, 'id': user.id}
return HttpResponse(json.dumps(user_dict))
except User.DoesNotExist:
user_dict = {'exists': 'false'}
return HttpResponse(json.dumps(user_dict))
What would be the correct way to test something like this? I am guessing I have to mimic a request somehow that gives me an email and then have two tests where one matches and one doesn't match an existing user and make sure each returns the appropriate object. Is this the correct way of thinking about it? Can someone help me out a bit with the syntax? I'm guessing using django.test.Client in some way would be appropriate?
What you need to test is if your code can not be broken. That means your code should always give a response and not generate any error in any case.
Since if the things you can test are-
1) authorization- is service accessible by any user or only authenticated ones, if only authenticated then test response for both kind of users, anonymous and authenticated. While testing response, test that you get 401 for anonymous users and 200 for authenticated ones.
2) response for non existing emails
3) response for users without a first_name if first_name is optional.
4) response data in case of valid request- is data received of correct user
There may be more tests, these from top of my head.