Django: Limit user's session by time - django

I'm working on the app where I need to limit the ability to log in and be authenticated for a specified time of the day. Let's say from 8am to 5pm. To limit the ability to log in I created a custom auth backend where authenticate() method returns user object only if current time is within allowed period of time.
Now I want to terminate user's auth session after specified time.
Setting session expiry_date date and cookie's Expiry seems to be the best way to achieve this, but after reading Django docs and digging in the source code for some time I did not found a good solution to it. How do I do this? Any help is appreciated.

Changing the auth backend is probably not the solution you are looking for (at least I wouldn't recommend it), since you are changing security-critical parts of your application.
I would suggest a custom middleware: If registered users trying to access your site between 8am and 5pm, they'll see a warning that this site cannot be used.
from django.utils import timezone
from django.core.exceptions import PermissionDenied
class AccessRestrictionMiddleware:
def process_request(self, request):
current_hour = timezone.now().hour
is_time_restricted = current_hour >= 8 and current_hour < 17
if request.user.is_authenticated() and is_time_restricted:
raise PermissionDenied

Related

Django caches user object

Our websites sometimes has around 600 authenticated users trying to register for an event in a timeframe of 5 min. We have a VPS with 1 CPU and 1GB ram. On these moments the site slows down and gives a 502 error.
For that reason I'm using per-site cache with FileBasedCache. This works excellent and stress tests work fine.
But, when people login, they're redirect to their profile. This is the code:
class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False
def get_redirect_url(self):
return reverse("users:detail", kwargs={"membership_number": self.request.user.membership_number})
the user is redirect to an url with their membership_number
class UserDetailView(LoginRequiredMixin, DetailView):
model = User
slug_field = "membership_number"
slug_url_kwarg = "membership_number"
Some users are reporting that they are redirected to someone else his/her profile after logging in.
How does this work? How to prevent user specific parts of the website to be cached? e.g. users also see a list of events that are specific to the groups they are in. In other words, every user should see a different list.
Any ideas? Best practices?
you should be able to vary cache on cookie so that logged in users (assuming cookie based auth) get another cache key.
from django.views.decorators.vary import vary_on_cookie
#vary_on_cookie
def my_view(request):
pass
https://docs.djangoproject.com/en/dev/topics/cache/#controlling-cache-using-other-headers
and
https://docs.djangoproject.com/en/dev/topics/cache/#using-vary-headers

Expiring a Django session at a specific time for a specific user (eg when their shift ends) at login

I have a system that has overlapping shift workers on it 24/7. Currently it is not uncommon for one to forget to log out and the next worker pick up their session and run with it. This causes some accountability issues.
I do realise there are options for session length ie settings.SESSION_COOKIE_AGE but these are a bit blunt for our purposes. We have different workers with different shift lengths, managers who have 2FA on-action and it's basically just not the path we want to pursue. Simply put...
I want to programmatically set the session death time on login.
We already have a custom Login view but this bubbles up through the built-in django.contrib.auth.forms.AuthenticationForm. And even there I can't see how to set an expiry on a particular session.
Any suggestions?
Edit: request.session's .get_expiry_age() and set_expiry(value) seem relevant but they do appear to update because they cycle around based on when the session was last modified, not when the session started. I need something that sets a maximum age on the session.
Edit 2: I guess I could write into the session on login and run something externally (a cronned management whatsit) that checked the expiries (if existed) and nuked each session that lapsed.
Came up with an answer thanks to the comments. On login I insert the timestamp into session:
request.session['login_timestamp'] = timezone.now().timestamp()
If you're wondering why timestamp, and not datetime.datetime.now() or timezone.now(), Django's default session encoder uses JSON and Django's JSON encoder does not handle datetimes. This is circumventable by writing an encoder that can handle datetimes... But just using an integer seconds-since-epoch value is enough for me.
And then have a little middleware to check that session against the current time.
from django.contrib.auth import logout
class MyMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# other checks to make sure this middleware should run.
# eg, this only happens on authenticated URLs
login_timestamp_ago = timezone.now().timestamp() - request.session.get('login_timestamp', timezone.now().timestamp())
if settings.RECEPTION_LOGIN_DURATION and <insert user checks here> and login_timestamp_ago >= settings.RECEPTION_LOGIN_DURATION:
logout(request) # nukes session
messages.warning(request, "Your session has expired. We need you to log in again to confirm your identity.")
return redirect(request.get_full_path())
The order of events here is quite important. logout(request) destroys the whole session. If you write a message (stored in session) beforehand, it'll be missing after the logout(request).

Django REST - Serialize User.get_all_permissions

I am building an application with a Django Rest backend, and a VueJS front end and am working through authorization and authentication. I have the authentication working well, but am a bit stuck on letting the front end (VueJS) know what the user has authorization to do in terms of Add/Change/View/Delete for a model. For example, if a user cannot add a customer, I don't want to show the 'Add Customer button'.
Working through the Django docs, and solutions on StackOverflow, I believe the simplest way is to send the user's permissions from Django to VueJS.
The 'best'/'simplest' way I can see to get the permissions is with the following:
userModel = User.objects.get(request.user)
return User.get_all_permissions(userModel)
Where I am stuck is exactly where to put this logic and how to serialize it. Does the above belong in the View, Serializer, other? Up until now, I have only been working with Models (ModelSerializers and ModelViews), but I don't believe this falls into this category.
Thanks in advance...
You should add this logic to views, because the views are used to implement these kinds of logic.
Actually, you don't want to use serializers here, because of the response of .get_all_permissions() method is already in serialized form
Apart from that, your provided code is not good (it's clearly bad). It should be as below,
return request.user.get_all_permissions()
because, you'll get current logged-in user's instance through request.user, to get his/her permissions, you all need to call the get_all_permissions() method
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
#permission_classes(IsAuthenticated, )
#api_view()
def my_view(request):
logged_in_user = request.user
return Response(data=logged_in_user.get_all_permissions())

Keep Django session data after sign-in?

I recently wrote shopping cart code that depends on the Session object. It seemed the reasonable way to store data for anonymous users.
While doing a bunch of testing, I ran into an annoying problem--when users sign in part way through the checkout process (or simply while browsing for other products), Django issues a new session_key and I lose access to my session data.
Is there a way to keep the old session data? Or is my design approach wrong?
Try writing your own SessionBackend that inherits from existing one and overrides the cycle_key method.
1 In your settings.py:
SESSION_ENGINE = 'my_app.session_backend'
2 my_app.session_backend.py:
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore
class SessionStore(DbSessionStore):
def cycle_key(self):
pass
cycle_key is beeing called in login view after authentication.
Let me now if it works ;)
Instead of disabling the cycle_key() (which is a security measure to avoid session fixation vulnerabilities), you could consider restoring the values through a decorator at the login and logout views. See:
https://stackoverflow.com/a/41849076/146289
I'm trying to do something similar. Django can change the session_key to mitigate session fixation vulnerabilities, so it's not suitable for a foreign key. I want something more permanent. So I'll just put the permanent identifier in request.session['visitor_id']:
from django.utils.crypto import get_random_string
import string
VALID_KEY_CHARS = string.ascii_lowercase + string.digits
def example_view(request):
if not request.session.get('visitor_id'):
self.request.session['visitor_id'] = get_random_string(32, VALID_KEY_CHARS)
# Now code the rest of the view, using the visitor_id instead of
# session_key for keys in your model.
# ...

Why Django 1.4 per-site cache does not work correctly with CACHE_MIDDLEWARE_ANONYMOUS_ONLY?

I am working on a Django 1.4 project and writing one simple application using per-site cache as described here:
https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache
I have correctly setup a local Memcached server and confirmed the pages are being cached.
Then I set CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True because I don't want to cache pages for authenticated users.
I'm testing with a simple view that returns a template with render_to_response and RequestContext to be able to access user information from the template and the caching works well so far, meaning it caches pages just for anonymous users.
And here's my problem. I created another view using a different template that doesn't access user information and noticed that the page was being cached even if the user was authenticated. After testing many things I found that authenticated users were getting a cached page if the template didn't print something from the user context variable. It's very simple to test: print the user on the template and the page won't be cached for an authenticated user, remove the user on the template, refresh the page while authenticated and check the HTTP headers and you will notice you're getting a cached page. You should clear the cache between changes to see the problem more clearly.
I tested a little more and found that I could get rid of the user in the template and print request.user right on the view (which prints to the development server console) and that also fixed the problem of showing a cached page to an authenticated user but that's an ugly hack.
A similar problem was reported here but never got an answer:
https://groups.google.com/d/topic/django-users/FyWmz9csy5g/discussion
I can probably write a conditional decorator to check if user.is_authenticated() and based on that use #never_cache on my view but it seems like that defeats the purpose of using per-site cache, doesn't it?
"""
A decorator to bypass per-site cache if the user is authenticated. Based on django.views.decorators.cache.never_cache.
See: http://stackoverflow.com/questions/12060036/why-django-1-4-per-site-cache-does-not-work-correctly-with-cache-middleware-anon
"""
from django.utils.decorators import available_attrs
from django.utils.cache import add_never_cache_headers
from functools import wraps
def conditional_cache(view_func):
"""
Checks the user and if it's authenticated pass it through never_cache.
This version uses functools.wraps for the wrapper function.
"""
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
if request.user.is_authenticated():
add_never_cache_headers(response)
return response
return _wrapped_view_func
Any suggestions to avoid the need of an extra decorator will be appreciated.
Thanks!
Ok, I just confirmed my "problem" was caused by Django lazy loading the User object.
To confirm it, I just added something like this to my view:
test_var = "some text" + request.user
And I got an error message telling me I couldn't concatenate an str to a SimpleLazyObject. At this point the lazy loading logic hasn't got a real User object yet.
To bypass the lazy loading, hence return a non-cache view for authenticated users, I just needed to access some method or attribute to triggers an actual query on the User object. I ended up with this, which I think it's the simplest way:
bypass_lazyload = request.user.is_authenticated()
My conditional_cache decorator is no longer needed, although it was an interesting exercise.
I may not need to do this when I finish working with my views as I'll access some user methods and attributes on my templates anyway but it's good to know what was going on.
Regards.