How to configure two AUTH_USER_MODEL in Django Authentication - python

I'm using django's authentication to login users. But I have two models from where authenticate method would check the user credentials. One is ApplicationUser and the other is SystemUser I have made one of them and it works fine like so:
models.py
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
"""
Creates and saves a User with the given username and password.
"""
....
return user
def create_superuser(self, email, password):
...
return user
class ApplicationUser(AbstractBaseUser):
application_user_id = models.AutoField(primary_key=True)
....
....
views.py
def login_view(request):
...
user = authenticate(username = email, password = password)
if user is not None:
...
login(request, user)
....
....
I came through this problem and got here but I couldn't work out a solution to this.
My Questions:
How do I specify two AUTH_USER_MODEL, as of yet I have set
ApplicationUser as AUTH_USER_MODEL.
And even if I somehow specify
the two AUTH_USER_MODEL, how do the authenticate or login function
know where (ApplicationUser or SystemUser) to match the credentials and create session for the user
accordingly

In your settings.py set
AUTH_USER_MODEL = 'yourapp.YouCustomUserModel'
Then let's django do the rest.
If you want to have others User Model, you need to extend them from your selected AUTH_USER_MODEL

Related

Different login views with Custom User

My goal is to have two views for login: one with email and password, and another with username and password.
Created a custom user class named MyUser extending from AbstractBaseUser. In that user class, stablished
USERNAME_FIELD = 'email'
Django by default uses the username field for authentication purposes of a user.
When i want users to login with email, that's easy now. But how can I do to create another view that logs in with just username and password (instead of the email)?
I do this by adding another authentication backend, using AUTHENTICATION_BACKENDS. Or you can write your own Auth backend, accepting either email or username.
Here's a simple version of an email authentication backend. Note that this requires that e-mail addresses are unique in your database, and does not case fold.
class EmailAuthBackend(ModelBackend):
"""
Email Authentication Backend
Allows a user to sign in using an email/password pair rather than
a username/password pair.
"""
def authenticate(self, request, email=None, password=None, **kwargs):
""" Authenticate a user based on email address as the user name. """
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
User().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user

Django Testing - authenticate() returns None

Working on making some unittests with Django, and trying to make some testing with the login process with the login form.
I am using modified User model just to make the email field unique; otherwise nothing drastically different.
account/views.py
def post(self, request):
# Retrieve the username and password
username = request.POST['username']
password = request.POST['password']
# Create a user object from authentication, or return None
user = authenticate(username=username, password=password)
# Check if user was created
if user is not None:
# Rest of the code, irrelevant...
account/test_views.py
from account.models import User as CustomUser
# Code snippit
def test_account_login_POST_successful_login(self):
# Create a user to test login
CustomUser.objects.create_user(
username='test_user',
email='test_user#intranet.com',
password='flibble'
)
response = self.client.post(self.login_url, {
'username': 'test_user',
'password': 'flibble'
})
self.assertEqual(response.status_code, 301)
account/models.py
class User(AbstractUser):
# Make the email field unique
email = models.EmailField(unique=True)
project/settings.py
# Authentication
AUTH_USER_MODEL = 'account.User'
Funny thing is that login works normally on the web app, but when testing it always returns None.
I've tried to check_password() with the created user, and it returns true in both the test method and the view method.
I've also tried putting in AUTHENTICATION_BACKEND = ['django.contrib.auth.backends.ModelBackend'], but no go.
I faced with the same problem. The problem was in is_active model field. In my case this field was False by default, so authenticate() returns None all the time.

How to create an authentication system in django for a SQLServer database?

I am recently working in a django project with a SQLServer database. I already connected the database with SQLServer, and I want to make an authentication system for a table I have in that database.
I know django comes with a built-in authentication system, but there is no way to tell django to use a specific table in the database to make the authentication, it just seems to look for users in the default admin page.
Is there any way for django to look for data inside a specific table in a SQLServer database and validate the information put by an user?
You can do it by implementing you own user model and then telling django how to authenticate the user. Your model should look something like this:
class Users(models.Model):
id = models.IntegerField(primary_key=True)
is_active = models.IntegerField(default=1)
date_joined = models.DateTimeField(default=timezone.now)
last_login = models.DateTimeField(default=timezone.now)
username = models.CharField(max_length=30, unique=True)
password = models.CharField(max_length=30)
#property
def is_authenticated(self):
return True
You can add extra fields, but those are required by django. The property is_authenticated shall always return true, as defined in the documentation.
Next step is to define how will your login authenticate. Create a file name backends.py anywhere in your project and inside of it declared two methods: authenticate and get_user, it should something like this:
from django.contrib.auth.backends import ModelBackend
from users.models import Users
from django.contrib.auth.hashers import *
from login.util import *
class PersonalizedLoginBackend(ModelBackend):
def authenticate(self, request=None, username=None, password=None, **kwars):
#Here define you login criteria, like encrypting the password and then
#Checking it matches. This is an example:
try:
user = Users.objects.get(username=username)
except Users.DoesNotExist:
return None
if check_password(password, user.password):
return user
else:
return None
def get_user(self, user_id):
#This shall return the user given the id
from django.contrib.auth.models import AnonymousUser
try:
user = Users.objects.get(id=user_id)
except Exception as e:
user = AnonymousUser()
return user
Now you need to tell django that where is the backends located, on your settings.py:
AUTHENTICATION_BACKENDS = (
# ... your other backends
'path.to.backends.PersonalizedLoginBackend',
)
From there you should be able to do your login like normal, first authenticate and then use do_login function.
Read more details in here:
https://docs.djangoproject.com/en/2.2/topics/auth/customizing/

Way to use email as the authentication field?

I am using django's inbuilt authentication system. Everything seems to be working fine. There are two fields that the user is requested to input at the time of signup: username and email. While logging in they are required to enter username and password.
I'd like to change this behavior so that username field is gone. I want to treat the email as the users username. So while signing in user will be required to put email / password
Is this possible while still using django's inbuilt auth system? I'm on django 1.7
Update
I had the need to add additional fields so I added the following to models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
telephone_number = models.CharField(max_length=100)
website_url = models.CharField(max_length=100)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
The answer is directly in django documentation, in short words you should subclass AbstractBaseUser or AbstractUser (in second case you can't totally remove username field), create your own user manager based on BaseUserManager or UserManager and customize built-in auth forms if you're using it (or any app that you're using is using it).
This is not strictly cannon, but to avoid creating a new User class or Auth backend, I tend to let users log in with both username or email.
Anyways you'll want to ensure emails are unique as django does not check this by default.
You'll then have to override the default login view to support this. You can create something along the lines of:
class EmailUsernameLoginView(View):
def post(self, request):
next = request.POST.get('next', None)
username = request.POST.get('username', None)
password = request.POST.get('password', None)
error = ''
if username and password:
try:
usr = User.objects.get(email=username)
username = usr.username
except User.DoesNotExist:
pass # If the user doesn't exist, it's an username
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect(next)
else:
error = 'Your account is not active'
else:
error = 'The username / email - password comb is wrong'
else:
error = 'Please provide a username / email and password'
ctx = {'error': error} # Fill with needed feedback
return render(request, ctx, 'registration/login.html')
This is just a draft and you should probably include a form to help with validation / cleaning

Django - Allow duplicate usernames

I'm working on a project in django which calls for having separate groups of users in their own username namespace.
So for example, I might have multiple "organizations", and username should only have to be unique within that organization.
I know I can do this by using another model that contains a username/organization id, but that still leaves this useless (and required) field on the defualt django auth User that I would have to populate with something.
I've already written by own auth backend that authenticates a user against LDAP. However, as I mentioned before, I am still stuck with the problem of how to populate / ignore the username field on the default django user.
Is there a way to drop the uniqueness constraint for the username for Django auth users?
I'm not sure if this is exactly what you're looking for, but I think you could use a hack similar to what is in this answer.
The following code works, as long as it is in a place that gets executed when Django loads your models.
from django.contrib.auth.models import User
User._meta.get_field('username')._unique = False
Note that this won't change the database unique constraint on the auth_user table if it has been already been created. Therefore you need to make this change before you run syncdb. Alternatively, if you don't want to recreate your auth_user table, you could make this change and then manually alter the database table to remove the constraint.
What you can do is extend the User model. For the User table, generate a username (e.g. A_123, A_345) that will not be displayed at all in the site.
Then create a new model that extends User.
class AppUser(User):
username = models.CharField...
organization = models.CharField...
You then create a custom authentication backend that use AppUser instead of the User object.
I have not personally been required to find a solution to this, but one way to tackle this (from an SAAS perspective) would be to prefix the username with an organizational identifier (presuming unique organizations). For example: subdomain.yoursite.com would equate to a user with the username: subdomain_username. You would just have to code some business logic on login to a subdomain to tack that onto the username.
I also suffered with this problem. I was doing a project where I had to use email and mobile no. as login fields but none of them should be unique because their were different types of users and a user can have more than one user entity and also the project required only one auth user table (Hectic right!).
So I extended AbstractBaseUser class where I could change the USERNAME_FIELD attribute. Here's how :-
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import PermissionsMixin
# Custom model for User
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(max_length=100, blank=False)
last_name = models.CharField(max_length=100, blank=True)
password = models.CharField(max_length=255, blank=False)
email = models.EmailField(max_length=255, blank=False)
mobile = models.CharField(max_length=12)
user_type = models.ForeignKey(UserTypes, on_delete=models.DO_NOTHING)
is_approved = models.BooleanField(default=False)
objects = UserManager()
# Here's the Catch
USERNAME_FIELD = 'id'
def get_full_name(self):
'''
Returns the first_name plus the last_name, with a space in between.
'''
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns the short name for the user.
'''
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
class Meta:
db_table = 'user'
Yes exactly, surprised? USERNAME_FIELD should be a unique field that's the constraint of this attribute. I couldn't use email or mobile no. as unique field.
Then I created a custom manager to remove the username field (reference = https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractbaseuser)
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
This will do the trick.
I'm facing the exact same problem and I've been reading a lot (about how to solve that problem in 1.5) and I just thought of a much simpler solution. What if you just add a fixed-length prefix with the organization id to store the username?
I.e. Organization id = 115, chosen username = "john" and a fixed length of 6. So in the data base you store as username "000115_john".
When you do the login you just join the two parameters and try to authenticate with what Django provides. I'm not sure if the fixed length is strictly necessary but could avoid undesirable results if a user chooses a username with only numbers.

Categories

Resources