Django Rest Framework Testing - django

I have a LoginSerializer that has the block of code as below
def validate(self, attrs):
username = attrs.get('username', '')
password = attrs.get('password', '')
user = auth.authenticate(username=username, password=password)
if user:
if user.is_active is False:
raise AuthenticationFailed(
'Account is disabled, contact admin')
if not user.is_verified:
raise AuthenticationFailed('Email is not verified')
return {
'username': user.username,
'firstname': user.firstname,
'lastname': user.lastname,
'role': user.role,
'tokens': user.tokens
}
else:
raise AuthenticationFailed('Invalid credentials, try again')
and a test case as below;
class UserLoginTest(BaseTest):
def test_inactive_user_can_login(self):
self.client.post(
self.register_public, data=valid_user, format='json')
user = User.objects.get(username=valid_user['username'])
user.is_verified = True
user.is_active = False
user.save()
response = self.client.post(
self.login_url, valid_login_user, format='json')
print(response.data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
When I run the test with is_active = False I get Invalid credentials, try again. Why is it that when is_active=False the user is not found even though the user is there? Same with when I try to login from swagger.
EDIT
I have read that I can use
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.AllowAllUsersModelBackend']
then I will be able to check for is_active manually otherwise django handles that and returns a None. What are the dangers of doing this?

This happens because you are using .authenticate() which by default goes through all backends listed in AUTHENTICATION_BACKENDS. If not listed in settings, django.contrib.auth.backends.ModelBackend is used, this backend verifies .is_active() field:
...
def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, "is_active", None)
return is_active or is_active is None
...
That snippet runs before your verification and returns None to user variable. So, it fails on if user condition (user is None) thus raise AuthenticationFailed('Invalid credentials, try again')
About AllowAllUsersModelBackend the only thing it does is override this method to allow inactive users to login:
class AllowAllUsersModelBackend(ModelBackend):
def user_can_authenticate(self, user):
return True
The only risk i can see, is using this backend and not checking .is_active() field manually. Unless if it is intended that inactive users can login into your system.

Related

Django Rest Framework Custom Authentication + return custom json

I am using django 2.1 and python 3.6....I want to authenticate users using custom authentication and return token along with email and id after validating user.
settinge.py
AUTHENTICATION_BACKENDS = (
"users.views.authentication_backend.UserAuthenticationBackend",
)
views.py
class UserAuthenticationBackend(object):
def authenticate(self, request, email=None, password=None):
validate_email(email)
valid_email = True
kwargs = {'email': email}
try:
user = get_user_model().objects.get(**kwargs)
except get_user_model().DoesNotExist:
raise exceptions.AuthenticationFailed('User does not exist.')
if valid_email and user.check_password(password) and user.is_valid is True:
return user
elif valid_email and user.check_password(password) and user.is_valid is False:
raise exceptions.AuthenticationFailed('Sorry your account is suspended temporarily,'
)
else:
raise exceptions.AuthenticationFailed('Sorry, your email and password does not match.')
I want to return response as follow:
{'token':"", 'user': {'id': 1, 'email': 'admin#abc.com', 'is_active': True}}
Thanks in advance...
You can use Token Authentication or even JSON Web Tokens(JWT)
These can help you get a token as a response TokenAuthentication,
JSONWebToken
These can be helpful for your requirement,If you need any other additional help please do mention it!!!

django have the ability to login with phone or email

I want to create a django custom user model such that i can have my users login with either phone or email.
This is my proposed solution
class ExtendedUser(AbstractBaseUser, PermissionsMixin):
phonenumber = PhoneNumberField(unique=True, null=True ..)
email = EmailField(unique=True, null=True ..)
...
USERNAME_FIELD = 'pk'
so now while login, i can do something like this
if cleaned_data['phonenumber']:
u = User.objects.get(phonenumber=cleaned_data['phonenumber'])
authenticate(username=u.pk, password=cleaned_data['password'])
...
elif cleaned_data['email']:
...
I am not sure whether it is possible to put USERNAME_FIELD as pk.
We can easily put a UUIDField if that's not possible.
Is the proposed solution fine?
The unique constrain for Email and Phone are good. Also, I would set USERNAME_FIELD = 'email'.
Then, I think you should try to create a custom authentication backend. You can check here
Like Django says: When somebody calls django.contrib.auth.authenticate(), Django tries authenticating across all of its authentication backends.
Then in your custom authentication backend you can ask for Email or Phone:
class CustomAuthenticationBackend:
def authenticate(self, request, email_or_phone=None, password=None):
try:
user = User.objects.get(
Q(email=email_or_phone) | Q(phone=email_or_phone)
)
pwd_valid = user.check_password(password)
if pwd_valid:
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I think it can work. Let me know!!
Best regards.
django login take two argoman : reqeust and user query so when we get this two things we can login. django authenticate searching in the user model and when user in found ....
def log_in(request):
if request.user.is_authenticated is True:
return redirect('/home/')
if request.method == 'POST':
u = request.POST.get('_phone_')
p = request.POST.get('_pass1_')
if '#' in u:
user_check = authenticate(request, email=u, password=p)
else:
user_check = MyUser.objects.get(phone= u) # search in our model and find user with phone
if user_check is not None:
user_check.last_login= datetime.now()
user_check.save(update_fields=['last_login'])
login(request, user_check)
return redirect('/home/')
else:
return HttpResponse('bad request')
else:
return render(request, 'login.html')

Error about Django custom authentication and login?

I create a custom Authentication backends for my login system. Surely, the custom backends works when I try it in python shell. However, I got error when I run it in the server. The error says "The following fields do not exist in this model or are m2m fields: last_login". Do I need include the last_login field in customer model or Is there any other solution to solve the problem?
Here is my sample code:
In my models.py
class Customer(models.Model):
yes_or_no = ((True, 'Yes'),(False, 'No'))
male_or_female = ((True,'Male'),(False,'Female'))
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100,blank = False, null = False)
password = models.CharField(max_length=100)
gender = models.BooleanField(default = True, choices = male_or_female)
birthday = models.DateField(default =None,blank = False, null = False)
created = models.DateTimeField(default=datetime.now, blank=True)
_is_active = models.BooleanField(default = False,db_column="is_active")
#property
def is_active(self):
return self._is_active
# how to call setter method, how to pass value ?
#is_active.setter
def is_active(self,value):
self._is_active = value
def __str__(self):
return self.name
In backends.py
from .models import Customer
from django.conf import settings
class CustomerAuthBackend(object):
def authenticate(self, name=None, password=None):
try:
user = Customer.objects.get(name=name)
if password == getattr(user,'password'):
# Authentication success by returning the user
user.is_active = True
return user
else:
# Authentication fails if None is returned
return None
except Customer.DoesNotExist:
return None
def get_user(self, user_id):
try:
return Customer.objects.get(pk=user_id)
except Customer.DoesNotExist:
return None
In views.py
#login_required(login_url='/dataInfo/login/')
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(name=username,password=password)
if user is not None:
if user.is_active:
login(request,user)
#redirect to user profile
print "suffcessful login!"
return HttpResponseRedirect('/dataInfo/userprofile')
else:
# return a disable account
return HttpResponse("User acount or password is incorrect")
else:
# Return an 'invalid login' error message.
print "Invalid login details: {0}, {1}".format(username, password)
# redirect to login page
return HttpResponseRedirect('/dataInfo/login')
else:
login_form = LoginForm()
return render_to_response('dataInfo/login.html', {'form': login_form}, context_instance=RequestContext(request))
In setting.py
AUTHENTICATION_BACKENDS = ('dataInfo.backends.CustomerAuthBackend', 'django.contrib.auth.backends.ModelBackend',)
This is happening because you are using django's login() function to log the user in.
Django's login function emits a signal named user_logged_in with the user instance you supplied as argument. See login() source.
And this signal is listened in django's contrib.auth.models. It tries to update a field named last_login assuming that the user instance you have supplied is a subclass of django's default AbstractUser model.
In order to fix this, you can do one of the following things:
Stop using the login() function shipped with django and create a custom one.
Disconnect the user_logged_in signal from update_last_login receiver. Read how.
Add a field named last_login to your model
Extend your model from django's base auth models. Read how
Thanks, I defined a custom login method as follows to get through this issue in my automated tests in which I by default keep the signals off.
Here's a working code example.
def login(client: Client, user: User) -> None:
"""
Disconnect the update_last_login signal and force_login as `user`
Ref: https://stackoverflow.com/questions/38156681/error-about-django-custom-authentication-and-login
Args:
client: Django Test client instance to be used to login
user: User object to be used to login
"""
user_logged_in.disconnect(receiver=update_last_login)
client.force_login(user=user)
user_logged_in.connect(receiver=update_last_login)
This in turn is used in tests as follows:
class TestSomething(TestCase):
"""
Scenarios to validate:
....
"""
#classmethod
#factory.django.mute_signals(signals.pre_save, signals.post_save)
def setUpTestData(cls):
"""
Helps keep tests execution time under control
"""
cls.client = Client()
cls.content_type = 'application/json'
def test_a_scenario(self):
"""
Scenario details...
"""
login(client=self.client, user=<User object>)
response = self.client.post(...)
...
Hope it helps.

User registration endpoint in Django Rest Framework

I'm using Django rest framework. I've written the following view to register a new user to the system.
#api_view(['POST'])
#csrf_exempt
#permission_classes((AllowAny, ))
def create_user(request):
email = request.DATA['email']
password = request.DATA['password']
try:
user = User.objects.get(email=email)
false = False
return HttpResponse(json.dumps({
'success': False,
'reason': 'User with this email already exists'
}), content_type='application/json')
except User.DoesNotExist:
user = User(email=email, username=email)
user.set_password(password)
user.save()
profile = UserProfile(user=user)
profile.save()
profile_serialized = UserProfileSerializer(profile)
token = Token(user=user)
token.save()
return HttpResponse(json.dumps({
'success': True,
'key': token.key,
'user_profile': profile_serialized.data
}), content_type='application/json')
Is there a better, slightly more secure way, of creating a user registration api in DRF that doesn't leave the endpoint so open to sql injection?
Forgive me to digress a little, but I can't help but wonder use could have gotten away with far less code, than you have if you had created a serializer and used a class-based view. Besides, if you had just created email as EmailField of serializer it would have automatically guaranteed the validation of email. Since you are using orm interface, risk of sql injection is much less than raw query in my opinion.
Sample Code:-
class UserList(CreateAPIView):
serializer_class = UserSerializer
class UserSerializer(ModelSerializer):
email = serializers.EmailField()
raw_password = serializers.CharField()
Something on these lines, Obviously I couldn't write entire code.
You could validate the email in your serializer using the validate-email-address module like this:
from validate_email_address import validate_email
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
def validate_email(self, value):
if not validate_email(value):
raise serializers.ValidationError("Not a valid email.")
return value
class Meta:
model = User
fields = ('email', 'password')
Also, you might consider a packaged auth/registration solution like Djoser.

Customize user authentication

Can anyone give more informations about customizing the Django authentication system. Well, I created a custom user in which I use the email as an identifier:
USERNAME_FIELD = 'email'
and I created a custom authentication backend, and I changed authentication backend to this new one in the settings file :
class Custom_Authentication(ModelBackend):
def authenticate(self, email=None, password=None):
try:
user = self.user_class.objects.get(email=email)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
#property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.AUTH_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
but now i don't know how to authenticate a custom user using his email, do I use :
from django.contrib import auth
def auth_view(request):
email = request.POST.get('email', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=email, password=password)
if user is not None:
auth.login(request, user)
return HttpResponseRedirect('/loggedin/')
else:
return HttpResponseRedirect('/invalid/')
I tried this and it seems that it doesn't recognize the user I created using the admin interface. Are there any other things that I have to change?
If you want to do a custom authentication I would suggest making a form for that. Make a form that takes email and password and then write the clean method to validate the two.
Try taking a look at this answer as an example.