Our Django deployment checks every night which active users can still be found in out LDAP directory. If they cannot be found anymore, we set them to inactive. If they try to login next time, this will fail. Here is our code that does this:
def synchronize_users_with_ad(sender, **kwargs):
"""Signal listener which synchronises all active users without a usable
password against the LDAP directory. If a user cannot be
found anymore, he or she is set to “inactive”.
"""
ldap_connection = LDAPConnection()
for user in User.objects.filter(is_active=True):
if not user.has_usable_password() and not existing_in_ldap(user):
user.is_active = user.is_staff = user.is_superuser = False
user.save()
user.groups.clear()
user.user_permissions.clear()
maintain.connect(synchronize_users_with_ad)
But if they are still logged in, this session(s) is/are still working. How can we make them invalid immediately? All settings of the session middleware are default values.
You can log them out using
from django.contrib.auth import logout
if <your authentication validation logic>:
logout(request)
... from within any view.
logout() Django docs here.
In addition to the login_required decorator, you could use the user_passes_test decorator to test if the user is still active.
from django.contrib.auth import user_passes_test
def is_user_active(user):
return user.is_active
#user_passes_test(is_user_active, login_url='/your_login')
def your_function(request):
....
You can use a session backend that lets you query and get the sessions of a specific user. In these session backends, Session has a foreign key to User, so you can query sessions easily:
django-qsessions (based on django's db, cached_db session backends)
django-user-sessions (based on django's db session backend)
Using these backends, deleting all sessions of a user can be done in a single line of code:
# log-out a user
user.session_set.all().delete()
Disclaimer: I am the author of django-qsessions.
Related
I'm trying to implement a custom authentication backend in Django but its behaving in a very strange way. The custom backend authenticate against a different model from the usual Django User model an upon successfully verifying the provided credentials the backend get or create our usual Django User instance which it returns. Login nevertheless doesn't work.
From the documentation I learnt that I just needed to inherit the django.contrib.auth.backends.BaseBackend and override the authenticate() method which I did. As I've mentioned the custom authenticate() method basically verifies a set of credentials (username and password) against ones stored in the database (custom model, not django.contrib.auth.models.User) which if matched it would get or create an instance of django.contrib.auth.models.User via a proxy model which I named Profile; basically the Profile model has a reference of both django.contrib.auth.models.User and my custom model. When logging in though I keep redirected to the login page (It's like Django logs me in but doesn't set something somewhere such that when I try accessing a protected resource I'm redirected back to login). Also when I login which a django.contrib.auth.models.User object it works just fine and I can access the protected pages. Following are the reasons I opted for this authentication approach.
I'm working with an existing database that has it's own User tables with very different schema than what Django provides.(Actually the only fields in this system User table similar to Django's are username, and password)
I utilized Django's inspectb management command to recreate the models of which I'm under strict instructions to leave unmanaged, you know, manage=False in the model's meta class. Basically I can't inherit Django's AbstractUser as it would require new fields to be added.
Thus the best approach I could think of was to use a proxy model that would link both django.contrib.auth.models.User and my custom unmanaged models with a custom Authentication Backend.
I've tried watching for certain variables such as the request.session dictionary and request.user all which are set but still can't login successfully. Also when I use credentials that are not in either of the two models I get a Invalid Credentials message in the login page (desirable behavior).
Actually, my custom authenticate() method works fine and returns a valid user, the issue I think lies in django.contrib.auth.login. What could be the problem?
Here is the my authenticate method
def authenticate(self, request, username=None, password=None):
try:
c_user = CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
return None
#pwd_valid = check_password(password, user.password)
if not c_user.password==password:
return None
#Get and return the django user object
try:
profile = Profile.objects.get(custom_user=c_user)
user = profile.user
#Create user if profile has none
if not user:
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
profile.user = user
profile.save()
#Create new profile if none exists
except Profile.DoesNotExist:
#Create new user for the profile
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
Profile.objects.create(
user = user,
custom_user = c_user
)
return user
Here is how I added the custom backend to settings.py
AUTHENTICATION_BACKENDS = [
'Authentication.custom.CustomAuth',
'django.contrib.auth.backends.ModelBackend'
]
Can't believe I'm answering my own question. After much debugging and reading documentation I realized I didn't override get_user method of the django.contrib.auth.backends.BaseBackend thus the default one which returns None was always called. I implemented it and it worked like a charm.
I'm looking for a way to separate the session handling for the admin part of a site and the frontend.
A person should be able to log in to the admin (only if he has is_staff and/or is_superuser).
He should have to be able to login with another username into frontend.
So basically it's like two separate sessions for the admin and frontend.
Login/Logout and Permission-check functionality is not a problem, different sessions is the problem. Any ideas?
Thanks,
Probably you can't do that unless you have a separate authentication system which is not provided by django. Means, you can use django's auth system for admin users and a separate auth system which for normal users. IMHO, its not very ideal solution if you don't have a separate auth system.
Alternativly, you can write a new middleware for this. In that middleware, you can check if the user is authenticated with an admin user, and if he/she is, then log him/her out of the application and redirect to login page.
Here is an example:
from django.contrib.auth import logout
class RestrictAdminUserInFrontend(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.find("/admin/") == -1 and request.user.is_authenticated and request.user.is_superuser:
logout(request)
return redirect('login-page')
response = self.get_response(request)
return response
And add that to MIDDLEWARE settings:
MIDDLEWARE = [
'other middlewares',
'path.to.RestrictAdminUserInFrontend'
]
So, in this approach your admin users will be forced to login using normal user's username and password. But admin site and frontend will use same session.
I'm trying to distinguish between a couple of Django authentication backends (which are external packages and I preferably don't want to modify them) in my views. django.contrib.auth docs says auth backends (settings.AUTHENTICATION_BACKENDS) will be tried in order and the first that does authenticate, will return and set request.user and if any raises an exception, authentication is refused. But it does not say how can I distinguish between requests depending on which backend has authenticated the user.
Is this possible? and how?
As explained in Django docs for authentication backends settings:
Once a user has authenticated, Django stores which backend was used to authenticate the user in the user’s session, and re-uses the same backend for the duration of that session whenever access to the currently authenticated user is needed. This effectively means that authentication sources are cached on a per-session basis
Actually, this information is stored when function login(request, user, backend=None) is used (see django.contrib.auth.__init__.py). After user has been authenticated, following session information are stored:
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
HASH_SESSION_KEY = '_auth_user_hash'
# [...]
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
So, you should check current request's session for key BACKEND_SESSION_KEY to find the backend used to authenticate user.
I'm using Django 1.8.4 on Python 3, and attempting to create an auth backend which validates a cookie from a legacy ColdFusion web site and create / log the Django user in after checking the value in a database. In settings, I am including the backend:
AUTHENTICATION_BACKENDS = (
'site_classroom.cf_auth_backend.ColdFusionBackend',
)
And the code for the backend itself; SiteCFUser is a model against the SQL Server database user model which contains the active cookie token value:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import SiteCFUser
class ColdFusionBackend(ModelBackend):
"""
Authenticates and logs in a Django user if they have a valid ColdFusion created cookie.
ColdFusion sets a cookie called "site_web_auth"
Example cookie: authenticated#site+username+domain+8E375588B1AAA9A13BE03E401A02BC46
We verify this cookie in the MS SQL database 'site', table site_users, column user_last_cookie_token
"""
def authenticate(self, request):
User = get_user_model()
print('Hello!')
token=request.COOKIES.get('site_web_auth', None)
print('Token: ' + token)
cookie_bites = token.split('+')
if cookie_bites[0] != "authenticated#site":
# Reality check: not a valid site auth cookie
return None
username = cookie_bites[1]
cf_token = cookie_bites[3]
try:
site_user = SiteCFUser.objects.using('mssqlsite').filter(cf_username=username)
except:
# No user found; redirect to login page
return None
if site_user[0].cftoken == cf_token:
try:
# Does the user exist in Django?
user = User.objects.get(username=username)
except:
# User does not exist, has a valid cookie, create the User.
user = User(username=username)
user.first_name = site_user[0].cf_first_name
user.last_name = site_user[0].cf_last_name
user.email = site_user[0].cf_email
user.save()
else:
return None
def get_user(self, user_id):
User = get_user_model()
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The problem is, the backend doesn't seem to be called when hitting a URL with a view with #login_required, or even trying to log in through a form with username and password. If I force an error by changing the name of the class in settings, or change the name of the class in cf_auth_backend.py, I do get an error. However, none of the print statements show up in the console. I'm clearly missing something here: any idea what I'm not doing right?
While the accepted answer might have helped the OP, it's not a general answer to the question's title.
Authentication back ends do work simply by listing them in AUTHENTICATION_BACKENDS. But they may appear to be ignored
for various reasons, e.g.:
urls.py needs to point to something like django.contrib.auth.views.login
url(r'^accounts/login/$', django.contrib.auth.views.login)
if it's pointing to some other authentication app. AUTHENTICATION_BACKENDS
may not work.
the authenticate() method must accept a password keyword, either through
password=None or **kwargs. Probably true for username too. It won't
be called if it doesn't accept that keyword argument.
Authentication backends doesn't work that way. They won't be called on each request or on requests where authentication is required.
If you want to log in user based on some cookie, you should call authentication in middleware.
I understand how to log a user in/out as well as authenticate within django, but one thing that is mission critical to a new project of mine.
I would like to have the user logged in (which I have), and I would like to then ask the user for their credentials again on certain pages.
I have one method through a EmployeeAuthenticatedMixin that I have made, which checks the POST data for the credentials. The main problem is the Mixin does not redirect, it merely serves up a page. So a user can hit the refresh button and resubmit the form, giving them access again.
Is there any way to ask for the user credentials and allow them access to the next page? Maybe an internal Django thing? Sessions? Messages?
You can log them out forcing them to log back in, using request(logout)
pseudo-coded
def confirm_crednetials(request)
logout(request)
render 'form'
or First prompt the user with a form if they do not have a cookie, you can check and set the cookie with this built in django method resp.set_cookie(foo, cookie) but after you authenticate the user.
if 'id' in request.COOKIES:
**render page
else:
authenticate_user(username=foo, password=bar)
resp.set_cookie(foo, cookie)
I wrote a signal that would fire after login:
from django.contrib.auth.signals import user_logged_in
import datetime
def reauthentication(sender, user, request, **kwargs):
request.session['last_login_time'] = str(datetime.datetime.now())
request.session.save()
user_logged_in.connect(reauthentication)
Then I wrote middleware to catch views that require reauthentication if the sessions last_login_time is older than 3 minutes.