Django user authentication: django_auth_ldap.backend.LDAPBackend - django

I don't understand how to use the LDAPBackend in django, all I want to do is to authenticate a user against LDAP. I have tried the following:
from django_auth_ldap.backend import LDAPBackend
auth = LDAPBackend()
user = auth.authenticate(username='my_uid',password='pwd')
At this point user is None and looking at tcpdump I can't see any connection attempt to the LDAP server.
settings.py
AUTH_LDAP_SERVER_URI = 'ldap.example.com'
AUTH_LDAP_USER_DN_TEMPLATE = 'uid=%(user)s,ou=People,dc=example,dc=com'
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_auth_ldap.backend.LDAPBackend',
)
The official django doc doesn't provide any snippet about how to use this backend in a view.
Many thanks for your help!

All of the documentation for django-auth-ldap is here. For debugging your configuration, you'll want to install a logging handler on the 'django_auth_ldap' logger; see Django's logging documentation for more on that.
At a glance, I would say that one problem is that AUTH_LDAP_SERVER_URI is not set to a URI; try something of the form ldap://ldap.example.com/. You'll also want to review the documentation for AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: this is an advanced and somewhat subtle option that you should only enable if you know that you need it.

Related

How to have different expiry times for Web and Mobile Apps in Django simple jwt?

I am currently using Django Rest Framework to serve a React JS application, but recently, we are adding support for a React Native application as well.
Now, as I use Django Simple jwt, here's the code for the expiry of the refresh and access tokens:
settings.py
ACCESS_TOKEN_LIFETIME = datetime.timedelta(hours=2)
REFRESH_TOKEN_LIFETIME = datetime.timedelta(days=3)
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': ACCESS_TOKEN_LIFETIME,
'REFRESH_TOKEN_LIFETIME': REFRESH_TOKEN_LIFETIME,
...
}
While this works really well on web, I do not want the phone app users to have to get logged out automatically every 3 days.
Is there a way to alter the refresh token lifetime based on the device that is asking for the tokens?
If yes, how?
Actually, I had the same issue. but I came up with an idea and I know there Should be some better solution to this.
What I did is I set the refresh token lifetime to the max time which I wanted e.g, 90 days max.
Then handled the expiration in frontends in different manners, in ReactJs frontend besides saving the JWT credentials I saved a new record as "LOGIN_TIMESTAMP" which stores the exact time when the refresh token was given by the Django backend at the beginning of the authentication scenario, to be exact it is on login action time. And each time the user is about to do something to the react state, ReactJS checks if it's less/equal to a specific amount of time which in my case is less than 86,400 seconds a.k.a 24 hours if so it carries on working as it is supposed to do. If it isn't less than that specific time it sends a log-out request to the Django backend which causes to move the current token to the Blacklisted Tokens model and then the user will be redirected to the login page.
In other frontend platforms e.g, Swift, React Native, Kotlin and etcetera, I rather do nothing and let the refresh token lifetime which was 90 days in my case do its job, and after refresh token expiration the user will be guided to Login View.
api/views.py
class BlackListTokenView(APIView):
def post(self, request):
try:
refresh_token = request.data["refresh_token"]
token = RefreshToken(refresh_token)
token.blacklist()
except Exception as e:
return Response({}, status = status.HTTP_400_BAD_REQUEST)
return Response({}, status = status.HTTP_200_OK)
api/urls.py
urlpatterns = [
path('user/logout/', BlackListTokenView.as_view(), name="blacklist")]
UPDATE:
During the last recent few weeks once again I was dealing with the same problem. Thank god finally I came up with a better solution that handles everything on Django's Back-End side. I've just released it as a package in PyPi and in a GIT Repo. The whole tutorial is also included.
It is based on djangorestframework-simplejwt so you still need this plugin in your project.
You can install it:
$ pip install simplejwt-multisessions
Briefly, simplejwt-multisessions not only supports two different refresh lifetimes but brings more features to your Django project e.g., managing the number of active sessions of different categories of lifetimes like extending lifetime policies and etc.
Once again for more info just check PyPi Documentation and Usage
After simplejwt_multisessions installation:
# Django Project settings.py
INSTALLED_APPS = [
# ...
"rest_framework_simplejwt.token_blacklist",
"simplejwt_multisessions",
# ...
]
and:
# Django Project settings.py
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': (
# ...
'rest_framework_simplejwt.authentication.JWTAuthentication'
)
# ...
}
leave SIMPLE_JWT settings in settings.py as it already is.
You need to add an extra settings:
# Django project settings.py
JWT_MULTISESSIONS = {
'SECRET': SECRET_KEY,
'LONG_SESSION':{
'REFRESH_TOKEN_LIFETIME': SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'ROTATE_REFRESH_TOKENS': False,
'UPDATE_LAST_LOGIN': False,
'BLACKLIST_AFTER_ROTATION': False,
'EXTEND_SESSION': True,
'EXTEND_SESSION_EVERY_TIME': False,
'EXTEND_SESSION_ONCE_AFTER_EACH': timedelta(days= 15),
'LIMIT_NUMBER_OF_AVAIL_SESSIONS': True,
'MAX_NUMBER_ACTIVE_SESSIONS': 5,
'DESTROY_OLDEST_ACTIVE_SESSION': True,
},
'SHORT_SESSION': {
'REFRESH_TOKEN_LIFETIME': timedelta(days= 1),
'ROTATE_REFRESH_TOKENS': False,
'UPDATE_LAST_LOGIN': True,
'BLACKLIST_AFTER_ROTATION': True,
'EXTEND_SESSION': False,
'EXTEND_SESSION_EVERY_TIME': False,
'EXTEND_SESSION_ONCE_AFTER_EACH': timedelta(hours= 12),
'LIMIT_NUMBER_OF_AVAIL_SESSIONS': True,
'MAX_NUMBER_ACTIVE_SESSIONS': 2,
'DESTROY_OLDEST_ACTIVE_SESSION': True,
}
}
remove all related urls to simple_jwt and then add these new urls:
root_of_your_django_project/urls.py
from simplejwt_multisessions.api.views import (
initializeSession,
refreshSession,
logout,
)
urlpatterns = [
#...
path('api/session/login/', initializeSession, name='initialize_session'),
path('api/session/refresh/', refreshSession, name='refresh_session'),
path('api/session/logout/', logout, name='logout'),
#...
]
Login
Requesting Long session login:
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"username": "theUsername", "password": "thePassword",
"secret_key": "the_Secret_Key", "session": "LONG",
"info": "sample info"}' \
http://localhost:8000/api/session/login/
...
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2NTc1OTU2MywiaWF0IjoxNjYzMTY3NTYzLCJqdGkiOiJkOWFiYTMzYWNmMDg0ODU2YjJjMjA4OWJiYTFjZDc0YSIsInVzZXJfaWQiOjF9.zGwSDoJzoCEhIqwg3D4bRAwrnJ0DKRKSGOzdyyw__ho",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjYzMTY4MTYzLCJpYXQiOjE2NjMxNjc1NjMsImp0aSI6IjBlZGZlMmY3MzhiMzRhOWFhYzQ4ZDhjYzAxOTVmZjEzIiwidXNlcl9pZCI6MX0.QCEqyFmXk5-yHZ4dYKnhNx80o9mgAYRdgFfUtgV1lQQ",
"session_id": "ozdyyw__ho-10"}
Refresh
curl \
-X POST \
-H "Content-Type: application/json" \
-d
'{"refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2NTc1OTU2MywiaWF0IjoxNjYzMTY3NTYzLCJqdGkiOiJkOWFiYTMzYWNmMDg0ODU2YjJjMjA4OWJiYTFjZDc0YSIsInVzZXJfaWQiOjF9.zGwSDoJzoCEhIqwg3D4bRAwrnJ0DKRKSGOzdyyw__ho",
"secret_key": "the_Secret_Key", "session": "LONG"}' \
http://localhost:8000/api/session/refresh/
...
{"access":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjYzMTcwNTA5LCJpYXQiOjE2NjMxNjc1NjMsImp0aSI6IjQxNWMxNjcwMmVkNTRkYWM5MDk0OTkyMmExMjcyMTdjIiwidXNlcl9pZCI6MX0.gdSQUmoSt_-ir87xngbC7YIvwNsAXJaAy0l4IRfuT1I"}
Logout
This API facilitates logging out the session. Necessary inputs: ["secret_key", "refresh"]. The secret_key mentioned in JWT_MULTISESSIONS settings in settings.py. "refresh" must be the refresh token the one that makes the request and is about to log out.
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"secret_key": "the_Secret_Key",
"refresh":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2NTc1OTU2MywiaWF0IjoxNjYzMTY3NTYzLCJqdGkiOiJkOWFiYTMzYWNmMDg0ODU2YjJjMjA4OWJiYTFjZDc0YSIsInVzZXJfaWQiOjF9.zGwSDoJzoCEhIqwg3D4bRAwrnJ0DKRKSGOzdyyw__ho"}' \ http://localhost:8000/api/session/logout/
{}
once again to see more available features of simplejwt_multisessions and their usage in detail check the PyPi Documentation or GIT Repo
You can simply modify the access token lifetime on relevant place after create the JWT. access_token.set_exp(lifetime=timedelta(days=2))
See below example:
from rest_framework_simplejwt.tokens import RefreshToken
from datetime import timedelta
def change_token_expire(user):
token = RefreshToken.for_user(user)
access_token = token.access_token
access_token.set_exp(lifetime=timedelta(days=2))
return {
"access_token": access_token,
"access_exp": access_token.payload.get("exp"),
"refresh_token": str(token),
}
You should check if the user is using a mobile, for that you have several libraries that satisfy this functionality. For example:
django-user_agents
from django_user_agents.utils import get_user_agent
def my_view(request):
user_agent = get_user_agent(request)
if user_agent.is_mobile:
# Do stuff here...
elif user_agent.is_tablet:
# Do other stuff...
I don't know which version of django you have but keep in mind that its latest update was for version 2.2. If this package doesn't work for your version you can use its dependency directly python-user-agents.
from user_agents import parse
def my_view(request):
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
if user_agent.is_mobile:
# Do stuff here...
elif user_agent.is_tablet:
# Do other stuff...
and finally you should add a mobile config
ACCESS_TOKEN_LIFETIME = datetime.timedelta(hours=2)
REFRESH_TOKEN_LIFETIME = datetime.timedelta(days=3)
ACCESS_TOKEN_LIFETIME_MOBILE = datetime.timedelta(hours=n)
REFRESH_TOKEN_LIFETIME_MOBILE = datetime.timedelta(days=n)
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': ACCESS_TOKEN_LIFETIME,
'REFRESH_TOKEN_LIFETIME': REFRESH_TOKEN_LIFETIME,
'ACCESS_TOKEN_LIFETIME_MOBILE': ACCESS_TOKEN_LIFETIME_MOBILE,
'REFRESH_TOKEN_LIFETIME_MOBILE': REFRESH_TOKEN_LIFETIME_MOBILE,
...
}

Django authentication with basic auth with LDAP support and no User model

This is my first Django project so please bear with me. I am creating a pure DRF project.
My requirement is that the user will call the REST APIs while sending the user creds in basic auth format which I need to validate against a LDAP server.
Also, each API with have the credentials sent and so I do not need to store the credentials at my end (in the default USER model)
I was aiming to use :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
)
} and
INSTALLED_APPS = [
'django.contrib.auth',
}
to be able to read the basic auth creds and then hoping to hook in:
AUTHENTICATION_BACKENDS = [
'django_auth_ldap.backend.LDAPBackend',
]
to authenticate the user at LDAP server.
questions:
-- is there any better approach ?
-- since I am only passing on the credentials to LDAP server to know if the creds are valid, I do not need the USER table in the database. I need it only for object modeling. How can I acheive this? would a Proxy model help? Can we have an abstract model as the AUTH_USER_MODEL?
or using a remote user authentication backend a better option?
To make a short answer, Yes you can make a class that replaces the User model, Like this:
class User(object):
def __init__(self, authenticated=False, email=None):
self.is_authenticated = authenticated
self.email = email
The real question is how to let your Authentication middleware returns this in your request.user. There are multiple ways (I've never used django-auth-ldap so may be I'm not writing the best practice for it.):
One solution could be:
is to inherit from this class django_auth_ldap.backend.LDAPBackend https://django-auth-ldap.readthedocs.io/en/latest/reference.html#django_auth_ldap.backend.LDAPBackend
from django_auth_ldap.backend import LDAPBackend
class CustomLDAP(LDAPBackend)
def get_user_model(self):
# return your custom user
and then you need to AUTHENTICATION_BACKENDS to
AUTHENTICATION_BACKENDS = [
'path.to.CustomLDAP',
]
I'm not sure if you want this DEFAULT_AUTHENTICATION_CLASSES
if you are working with django-auth-ldap. May be this LDAP authentication with django REST
also help
READ THIS FIRST
I think also here https://django-auth-ldap.readthedocs.io/en/latest/users.html they explain it well. So, may be the solution lies in package itself

Registration with django-rest-auth: where to set allauth settings (email verification/required username)

I'm using django-rest-auth with the registration add-on, which uses django-allauth to implement signup, confirmation, social sign-in, etc endpoints. It's largely working, however I can't figure out where to put certain settings such that the registration and other auth endpoints will respect various allauth settings. My current issues are:
Users can login without verifying their email- the allauth setting ACCOUNT_EMAIL_VERIFICATION (default='optional') controls whether this is mandatory
Users must provide a username at signup, despite one not being required by my user model (email address is my username field)- the allauth setting ACCOUNT_USERNAME_REQUIRED (default=True) controls this
I've attempted to add the above settings to resolve these issues, but the registration and login endpoints don't seem to respect them. For example, from my settings.py:
....
ALLAUTH = {
'ACCOUNT_EMAIL_VERIFICATION': 'mandatory',
'ACCOUNT_USERNAME_REQUIRED': False,
}
....
However, in looking at both the allauth and django-rest-auth serializers, the allauth settings are checked in the following manner (from the django-rest-auth LoginSerializer):
# If required, is the email verified?
if 'rest_auth.registration' in settings.INSTALLED_APPS:
from allauth.account import app_settings
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
email_address = user.emailaddress_set.get(email=user.email)
if not email_address.verified:
raise serializers.ValidationError(_('E-mail is not verified.'))
Looking at the django-rest-auth/registration RegisterSerializer is similar:
try:
from allauth.account import app_settings as allauth_settings
from allauth.utils import (email_address_exists,
get_username_max_length)
from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email
except ImportError:
raise ImportError('allauth needs to be added to INSTALLED_APPS.')
....
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_settings.USERNAME_MIN_LENGTH,
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
password1 = serializers.CharField(required=True, write_only=True)
password2 = serializers.CharField(required=True, write_only=True)
In both places, the allauth settings are imported directly from allautha.account.app_settings instead of settings.py, as I'd expect. In the allauth source on github, I can see the app_settings file, however does that mean I need to place my desired settings there? Isn't the point of settings.py to be a single spot to collate settings for installed apps?
Figured it out- this turned out to be pretty easy, and I guess something I could have gathered from the docs, though its far from clear.
Rather than defining allauth settings in settings.py like this:
....
ALLAUTH = {
'ACCOUNT_EMAIL_VERIFICATION': 'mandatory',
'ACCOUNT_USERNAME_REQUIRED': False,
}
....
It needs to be done like this:
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_USERNAME_REQUIRED = False
Just directly in settings.py, no identifier needed. I'm not sure why they aren't in their own section, but this definitely works. Hopefully you don't have another app with identically named settings- though I'm sure there's a way around that.
I found this question + answer that clued me in.

How to test Django error reporting

In my settings I have this...
ADMINS = (
('Me', 'me#me.com'),
)
MANAGERS = ADMINS
DEBUG = False
ALLOWED_HOSTS = ['*']
and then in my views/urls I have these...
url(r'^test/$', 'main.views.test', name='page'),
def test(request):
return render(request, '500.html', status=500)
I have the email settings configured and I know they work because I have emails working from that server in other parts of my site. I have everything that is said to be required in the docs, but it is still not working and IDK where to go from here. I have tried it in my live environment too with no luck...
It turns out that my email settings were correct, but there was a new setting added for error reporting titled 'SERVER_EMAIL' that was supposed to be the default from email. I had a nonexistant email address in there and that is what was causing the emails to not be sent.

Create users in LDAP using Django

I am having trouble with the LDAP authentification module django-auth-ldap. I am using the example configuration from this site: http://packages.python.org/django-auth-ldap/
I'd like to do two things:
1) Authentificate against LDAP:
For the moment, my LDAP database is empty, I didn't add anything to it, in fact I don't know how to. However, I still am able to log in into my django-based site with my old logins/passwords stored in my django database. Why is that? Shouldn't this be ignored, shouldn't the login process occur with LDAP user/passwords instead? In other words, if my LDAP database is empty, shouldn't every single of my login fail? However, it doesn't, I have the impression that django completly ignores the django-auth-ldap module.
2) Synchronize LDAP with django (and not the other way around)
I don't want to use an existing user database to authentificate against. I want to be able to create new users in Django and propagate these users to LDAP so they can be shared by other services, in my case, an openfire server. How do you do that with django-auth-ldap?
Here is the copy/paste of my configuration:
# Baseline configuration.
AUTH_LDAP_SERVER_URI = "127.0.0.1"
AUTH_LDAP_BIND_DN = "cn=admin,dc=nodomain"
AUTH_LDAP_BIND_PASSWORD = "admin"
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=nodomain",
ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=django,ou=groups,dc=nodomain",
ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)"
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
# Only users in this group can log in.
AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=nodomain"
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTH_LDAP_PROFILE_ATTR_MAP = {
"employee_number": "employeeNumber"
}
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=django,ou=groups,dc=nodomain",
"is_staff": "cn=staff,ou=django,ou=groups,dc=nodomain",
"is_superuser": "cn=superuser,ou=django,ou=groups,dc=nodomain"
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
Sorry I don't know much about LDAP, I just installed it this morning so my question may sound naive. I just need a centralized user base that I would be able to update and share between several servers.
Thanks very much for your help.
1) Your configuration has two authentication backends installed:
AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', 'django.contrib.auth.backends.ModelBackend', )
Django will attempt to authenticate against each one in turn until it finds one that succeeds (or until it runs out). Since your LDAP directory is empty, it will presumably always fail, so ModelBackend will always get a shot. If you don't want to authenticate users against the Django user database, you have to remove ModelBackend from the list.
2) django-auth-ldap doesn't propagate Django users up to LDAP, only the other way around. It's designed to allow Django deployments to authenticate against existing LDAP services that are managed separately. To manipulate the contents of an LDAP directory from a Django app you might want to look at django-ldapdb.