I'm having a problem with a custom Authentication Backend I've built for an Active Directory via LDAP authentication.
The problem is that from the admin login page, after it properly authenticates and creates the new user in the database (or updates their info from the LDAP server), but then returns me to the admin login page indicating that I failed to enter a valid username and password.
Considering it authenticates and creates/updates the user in the django database, what am I doing wrong?
The code:
import ldap
import re
from django.conf import ad_settings
grps = re.compile(r'CN=(\w+)').findall
def anyof(short_group_list, adu):
all_groups_of_user = set(g for gs in adu.get('memberOf',()) for g in grps(gs))
return any(g for g in short_group_list if g in all_groups_of_user)
class ActiveDirectoryBackend(ModelBackend):
"""
This backend utilizes an ActiveDirectory server via LDAP to authenticate
users, creating them in Django if they don't already exist.
"""
def authenticate(self, username=None, password=None):
con = None
ldap.set_option(ldap.OPT_REFERRALS, 0)
try:
con = ldap.initialize('ldap://%s:%s' % (ad_settings.AD_DNS_NAME,
ad_settings.AD_LDAP_PORT))
con.simple_bind_s(username+"#"+ad_settings.AD_DNS_NAME, password)
ADUser = con.search_ext_s(ad_settings.AD_SEARCH_DN,
ldap.SCOPE_SUBTREE,
"sAMAccountName=%s" % username,
ad_settings.AD_SEARCH_FIELDS)[0][1]
con.unbind()
except ldap.LDAPError:
return None
# Does user belong to appropriate AD group?
if not anyof(ad_settings.PROJECTCODE,ADUser):
return None
# Does user already exist in Django?
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
#create Django user
user = User(username=username, is_staff = True, is_superuser = False)
#Update User info from AD
if ADUser.has_key('givenName'):
user.first_name = ADUser.get('givenName')[0]
if ADUser.has_key('sn'):
user.last_name = ADUser.get('sn')[0]
if ADUser.has_key('mail'):
user.email = ADUser.get('mail')[0]
# Does not store password in Django.
user.set_unusable_password()
user.save()
return user
EDIT: Figured out. Users cannot log in unless they are active (even though the documentation does not say that). Therefore, in the code given, the line that creates the new user should look like:
user = User(username=username, is_staff = True, is_Active = True,
is_superuser = False)
Figured out. Users cannot log in unless they are active (even though the documentation does not say that). Therefore, in the code given, the line that creates the new user should look like:
user = User(username=username, is_staff = True, is_Active = True,
is_superuser = False)
Related
I customized my user authentication. Using Django 3.
In models.py I have;
class User(AbstractUser):
is_patient = models.BooleanField(default=False)
is_dentist = models.BooleanField(default=False)
is_manager = models.BooleanField(default=False)
In views.py I have below for user creation and login;
def create_account_dentist(request):
if request.method == 'POST':
#Create Dentist Type User
username = request.POST.get('username')
password = request.POST.get('password')
mobile_num = request.POST.get('mobilenum')
user_profile = User()
user_profile.username = username
user_profile.email = username
user_profile.set_password(password)
user_profile.is_dentist = True
user_profile.is_staff = True
user_profile.is_active = True
user_profile.mobile_number = mobile_num
user_profile.save()
login(request, user_profile)
return redirect(reverse('dentist-section'))
My sttings.py : AUTH_USER_MODEL = 'myapp.User'
When i check /admin page i see only groups section.
But i want to see the users, permissions and user groups together.
My DB tables are :
auth_group,
auth_group_permissions,
auth_permission,
django_admin_log,
django_content_type,
django_migrations,
django_session,
myapp_dentist,
myapp_manager,
myapp_patient,
myapp_user,
myapp_user_groups,
myapp_user_user_permissions
Although i created users in myapp_user, i dont see and manage them in django admin page
Below is screeshot from myapp_user table :
You can check the admin.py file which is under the user's model folder, isn't it contain the admin.site.register(YUOR_MODEL_NAME) ,if you want to view the data in /admin page must have registered.
from django.contrib import admin
from YOUR_APP_NAME.models import User
admin.site.register(User)
I'm following a tutorial (https://thinkster.io/tutorials/django-json-api/authentication) on setting up authentication. I've got to the point where I'm registering users and receiving the tokens back. I can drop into the shell and do;
>>> user = Users.objects.first()
>>> user.email
>>> test#outlook.com
>>> user.password
>>> 12345678
This shows that the user exists in the database. But when calling my login endpoint;
/users/login/
the authenticate method returns None.
user = authenticate(username=email, password=password)
I'm printing the email and password just before and it shows the correct data passed in.
I've also set my USERNAME_FIELD to email in my model.
USERNAME_FIELD = 'email'
I've updated my model in my settings to be
AUTH_USER_MODEL = 'aemauthentication.User'
I've had a look around and the above line in the settings file seems to be the approved answer for most people with this issue.
GitHub link to project - https://github.com/Daniel-sims/aem_1
As per; Django User Creation successful, but authenticate() returning None
I'd changed the create_user method to take the password as a parameter;
user = self.model(username=username, email=self.normalize_email(email), password=password)
but you have to call set password, so everythings working fine with;
user = self.model(username=username, email=self.normalize_email(email))
user.set_password(password)
I am working on a Django Project:
I have a login form with username and password
Generally i observed authenticate function is used to authenticate a user and pass i.e
user = authenticate(username, password)
I tried to understand the authenticate function but i found it is not so easy.
Instead of using the authenticate function i want to check the authentication the following way:
1) check if the username exists
2) check if the password matches
3) check if is_active is true.
Which i can do by the following way:
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
# check if user exists
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
#check if user is_active
if user.is_active:
user_active = true
else
user_active = false
#check the password
if user_active:
password_match = user.check_password(password):
else:
password_match = false
if password_match:
msg = "Login Successful"
else:
msg = "Login Failed"
Will the above serve the same purpose as authenticate function or is there something else to be checked.
authenticate method runs through all authenticate backends and call it's authenticate method.
Default Django's auth backend is ModelBackend. If you check it's authenticate method you'll see that it's already include all described by you steps.
My custom user model:
class MyUser(AbstractBaseUser):
username = models.CharField(unique=True,max_length=30)
email = models.EmailField(unique=True,max_length=75)
is_staff = models.IntegerField(default=False)
is_active = models.IntegerField(default=False)
date_joined = models.DateTimeField(default=None)
# Use default usermanager
objects = UserManager()
USERNAME_FIELD = 'email'
Is there a way to specify multiple USERNAME_FIELD ? Something like ['email','username'] so that users can login via email as well as username ?
The USERNAME_FIELD setting does not support a list. You could create a custom authentication backend that tries to look up the user on the 'email' or 'username' fields.
from django.db.models import Q
from django.contrib.auth import get_user_model
MyUser = get_user_model()
class UsernameOrEmailBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
try:
# Try to fetch the user by searching the username or email field
user = MyUser.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password):
return user
except MyUser.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
MyUser().set_password(password)
Then, in your settings.py set AUTHENTICATION_BACKENDS to your authentication backend:
AUTHENTICATION_BACKENDS = ('path.to.UsernameOrEmailBackend,)\
Note that this solution isn't perfect. For example, password resets would only work with the field specified in your USERNAME_FIELD setting.
We can do that by implementing our own Email authentication backend.
You can do something like below:
Step-1 Substite the custom User model in settings:
Since we would not be using Django's default User model for authentication, we need to define our custom MyUser model in settings.py. Specify MyUser as the AUTH_USER_MODEL in the project's settings.
AUTH_USER_MODEL = 'myapp.MyUser'
Step-2 Write the logic for the custom authentication backend:
To write our own authentication backend, we need to implement atleast two methods i.e. get_user(user_id) and authenticate(**credentials).
from django.contrib.auth import get_user_model
from django.contrib.auth.models import check_password
class MyEmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
my_user_model = get_user_model()
try:
user = my_user_model.objects.get(email=username)
if user.check_password(password):
return user # return user on valid credentials
except my_user_model.DoesNotExist:
return None # return None if custom user model does not exist
except:
return None # return None in case of other exceptions
def get_user(self, user_id):
my_user_model = get_user_model()
try:
return my_user_model.objects.get(pk=user_id)
except my_user_model.DoesNotExist:
return None
Step-3 Specify the custom authentication backend in settings:
After writing the custom authentication backend, specify this authentication backend in the AUTHENTICATION_BACKENDS setting.
AUTHENTICATION_BACKENDS contains the list of authentication backends to be used. Django tries authenticating across all of its authentication backends. If the first authentication method fails, Django tries the second one, and so on, until all backends have been attempted.
AUTHENTICATION_BACKENDS = (
'my_app.backends.MyEmailBackend', # our custom authentication backend
'django.contrib.auth.backends.ModelBackend' # fallback to default authentication backend if first fails
)
If authentication via MyEmailBackend fails i.e user could not be authenticated via email, then we use the Django's default authentication ModelBackend which will try to authenticate via username field of MyUser model.
No, you cannot have more than one field defined in USERNAME_FIELD.
One option would be to write your own custom login to check for both fields yourself. https://docs.djangoproject.com/en/1.8/topics/auth/customizing/
i.e. change the backend to your own. AUTHENTICATION_BACKENDS then write an authenticate method and check the username on both fields in the DB.
PS you may want to use unique_together on your model so you don't run into problems.
Another option would be to use the actual field username to store both string and email.
Unfortunately, not out-of-the box.
The auth contrib module asserts that the USERNAME_FIELD value is mono-valued.
See https://github.com/django/django/search?q=USERNAME_FIELD
If you want to have a multi-valued USERNAME_FIELD, you will either have to write the corresponding logic or to find a package that allow it.
If your USERNAME_FIELD is username and the user logs in with email, maybe you can write a code that fetches the username using the provided email and then use that username along with the password to authenticate.
REQUIRED_FIELDS = []
you can define multiple username_fields
I have run into the following error trying to create a user in django:
>>> email = 'verylongemail#verylongemail.com'
>>> user_object = User.objects.create_user(username=email, email=email, password='password')
Data truncated for column 'username' at row 1
It seems Django has a limit on the number of chars allowed in a username. How would I get around this?
I've had to modify the auth_user table by hand to make the field longer and then convert emails into a username by removing the # symbol and the period (maybe other characters too, it's really not a great solution). Then, you have to write a custom auth backend that authenticates a user based on their email, not the username, since you just need to store the username to appease django.
In other words, don't use the username field for auth anymore, use the email field and just store the username as a version of the email to make Django happy.
Their official response on this topic is that many sites prefer usernames for auth. It really depends if you are making a social site or just a private site for users.
If you override the form for Django users you can actually pull this off pretty gracefully.
class CustomUserCreationForm(UserCreationForm):
"""
The form that handles our custom user creation
Currently this is only used by the admin, but it
would make sense to allow users to register on their own later
"""
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
class Meta:
model = User
fields = ('first_name','last_name','email')
and then in your backends.py you could put
class EmailAsUsernameBackend(ModelBackend):
"""
Try to log the user in treating given username as email.
We do not want superusers here as well
"""
def authenticate(self, username, password):
try:
user = User.objects.get(email=username)
if user.check_password(password):
if user.is_superuser():
pass
else: return user
except User.DoesNotExist: return None
then in your admin.py you could override with
class UserCreationForm(CustomUserCreationForm):
"""
This overrides django's requirements on creating a user
We only need email, first_name, last_name
We're going to email the password
"""
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
# let's require these fields
self.fields['email'].required = True
self.fields['first_name'].required = True
self.fields['last_name'].required = True
# let's not require these since we're going to send a reset email to start their account
self.fields['username'].required = False
self.fields['password1'].required = False
self.fields['password2'].required = False
Mine has a few other modifications, but this should get you on the right track.
You have to modify the username length field so that syncdb will create the proper length varchar and you also have to modify the AuthenticationForm to allow greater values as well or else your users won't be able to log in.
from django.contrib.auth.forms import AuthenticationForm
AuthenticationForm.base_fields['username'].max_length = 150
AuthenticationForm.base_fields['username'].widget.attrs['maxlength'] = 150
AuthenticationForm.base_fields['username'].validators[0].limit_value = 150