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.
Related
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
I'm trying to use BCryptAuth to protect resource as well as for login system.
I'm trying to fetch only one document based on user's email entered at login page.
class BCryptAuth(BasicAuth):
def check_auth(self, email, password, allowed_roles, resource, method):
account = app.data.driver.db['users'].find_one({'email': email})
return account and \
bcrypt.hashpw(password.encode('utf-8'),account['salt'].encode('utf-8')) == account['password']
But when i try to access the users end point via postman, it actually authenticates but returns all documents. I'm bit confused. If my approach is wrong, pls provide me one.
The Auth class you mention will only allow or not the access to the API. It does nothing for resource filtering.
If you want resource filtering when getting users, you can create an event hook and make a pre-GET dynamic filter. Check the documentation, it should help.
I apologize if there has been a substantial answer on this already, I have searched for quite a while and can't find any helpful answers.
I have a django project that has varying levels of account access. I have a group of 'Managers' and I want to allow them to manage user accounts.
I want the Managers to be able to create the user account objects for the users. However, I don't want them to have to deal with creating their passwords and sending them to the users (for obvious reasons). This way, managers can impersonate users to get work done on their behalf, and users maintain password control and ownership of their data.
The idea is that managers create the account, and when the account is created Users will be sent a password reset form (same as django's out-of-box auth) which will allow them to set their passwords.
My code looks similar to below (omitted non-imperative stuff)
from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordResetForm
#manager_required
def manager_add_users(request):
add_user_form = manager_add_user_form(request.POST)
new_user_name = add_user_form.cleaned_data['user_name']
new_user_email = add_user_form.cleaned_data['user_email']
new_user = User.objects.create_user(
username = new_user_name,
email = new_user_email,
)
new_user.save()
set_password_form = PasswordResetForm({'email': new_user.email })
if set_password_form.is_valid():
print 'Reset Form Is Valid'
set_password_form.save(
request= request,
use_https=True,
from_email="support#org.com",
email_template_name='registration/password_reset_email.html')
The account creates properly and everything runs without error. The issue is that although the form is valid (reset form is valid statement prints) it is not actually sending the reset form. There are no form errors.
However, in testing when I initialize the form with an address that already exists in the system like this:
set_password_form = PasswordResetForm({'email':'existing_address#example.com'})
The password reset form sends without error. So it only works with existing user email addresses in the system, but although the user has been created and the .save() method called, it's still not catching it (The users are marked as 'Active' upon creation, so they should be able to be found)
I'm a bit at a loss. I can think of a few ways that I could get around this issue, but this seems the most direct way of doing it and I'm really not entirely sure why it doesn't work.
Yes, I am able to send messages. I am using django's backend mail for testing:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Glancing at the source code, it looks like Django is ignoring the request because the password is blank.
Try setting a temporary password (using, say, django.utils.crypto.get_random_string()) before saving the user.
I have a method that grabs POST data that is formated in json format like this
[{"UserName": "alexgv", "Password": "secretpassword"}]
Here is the method
def Login(request, *args):
data = request.DATA
return Response(data)
"""
try:
m = User.objects.get(UserName=request.DATA['UserName'])
if m.password == request.DATA['Password']:
request.session['member_id'] = m.id
return HttpResponse("Testing")
except User.DoesNotExist:
return HttpResponse("Your username and password didn't match.")
"""
I want to be able to take just one variable from that json POST. For example, maybe I just want to grab the UserName or Password. How would I do that? I have tried a variety of things but cant seem to get it to work, and I dont want to use request.POST.get because then that means I would have to send POST variables. BTW I am using this http://django-rest-framework.org/. I have read through the docs but cant seem to find anything in there. Any help is appreciated. What it returns right now is everything.
Like so...
username = request.DATA['UserName']
Incidentally, you probably shouldn't be writing session based API login views yourself as it's easy to do wrong.
For APIs that provide AJAX style functionality the you have two good options:
Login using a standard Django login, performed by the user, not performed by the API client.
Use a credentials based authentication scheme, rather than session based, and perform the login using AJAX. For example the Djoser third party package is a great library including token-based login and other similar views... https://github.com/sunscrapers/djoser
Update Also discovered https://github.com/JamesRitchie/django-rest-framework-sav which might be worth a look for AJAX session based authentication.
I've been using django-social-auth (https://github.com/omab/django-social-auth), with some success - logging in from Twitter and Facebook have been set up without difficulty. However, when I log in from some OpenID providers, I am faced with an empty variable for the username, and the social-auth app allows this (despite me having set SOCIAL_AUTH_DEFAULT_USERNAME).
Whilst if SOCIAL_AUTH_DEFAULT_USERNAME worked properly that might be an interim solution, ideally I'd rather that it was either set automatically from the openID provider. Basically, I'm left with two possible solutions:
1) Make a custom method to extract some of the extra data sent from the openID provider to set the username from that.
2) Force the user to set a username when they first login.
Whilst (2) is less elegant, it ensures that a username has been inserted each time, and also obviates the need to have some postpocessing of the automatic information which may not be in a suitable format for the username.
My question amounts to, how can I go about implementing either of the above! Complete answers are not necessary, but pointers to a method would be much appreciated!
The alternative is to play with django-socialregistration and to see whether that makes life easier!
J
1 is probably the way to go. You can override the get_user_details method of the Backend class for the social providers you're having trouble with and pull the username from there.
It'd look something like this, for example for Facebook:
from social_auth.backends.facebook import FacebookBackend
class CustomFacebookBackend(FacebookBackend):
name = 'facebook'
def get_user_details(self, response):
"""Return user details from Facebook account"""
return {'username': response['name'],
'email': response.get('email', ''),
'first_name': response.get('first_name', ''),
'last_name': response.get('last_name', '')}
The "response" variable is the deserialized data returned from the request to the OAuth provider for the user details, so you can pull whatever you need from that, or inject your own values here. social-auth will take whatever you stick in "username" and use that as the username for the new account.
If you want more control, you can try overriding the backend's "username" method as well.
When overriding the backend, don't forget to add it to AUTHENTICATION_BACKENDS in settings.py. Also, I'm not sure how this works exactly, but I think you need to add something like this to the file with your custom backend in order to get social-auth to link your backend in properly:
BACKENDS = {
'facebook': CustomFacebookAuth,
}