I am a really new bird to python and using Django rest framework. I have followed the tutorial "http://www.django-rest-framework.org/tutorial/quickstart" and created a login logout page for admin. I wanted to track the session time or basically for how long the user has been logged in, I tried doing the same by following many tutorials like these 'https://github.com/GetBlimp/django-rest-framework-jwt' but I think I'm deviating from what I want, please suggest any tutorial or any help.
Thanks
It looks to me that this is not stored in the Sessions, but nothing prevents us from storing it ourselves.
We can for example construct a model UserSession:
# models.py
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
class UserSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
session = models.OneToOneField(Session, on_delete=models.CASCADE)
session_start = models.DateTimeField(auto_now_add=True)
Of course now we still need to ensure that such object is constructed, but we can create a hook for the login procedure:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver
#receiver(user_logged_in)
def register_session(sender, user, request, **kwargs):
# save current session
request.session.save()
# create a link from the user to the current session (for later usage)
UserSession.objects.get_or_create(
user=user,
session=Session.objects.get(pk=request.session.session_key)
)
We can then obtain the time the session started with:
from datetime import datetime
def some_view(request):
try:
session_started = request.session.usersession.session_start
except UserSession.DoesNotExist:
# ...
# handle the case when the UserSession is not present
pass
else:
session_duration = datetime.now() - session_started
# ...
pass
Related
I wanted to limit users to one login per account. I read numerous posts, and I've been following this post in particular: How to prevent multiple login in Django.
In myapp/models.py, I have this:
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
...
class UserSession(models.Model):
user_acc = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
session_manager = models.OneToOneField(Session, on_delete=models.CASCADE)
In myaurth/views.py, I import from myapp/models.py and have this:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver
...
#receiver(user_logged_in)
def remove_other_sessions(sender, user, request, **kwargs):
# remove other sessions
Session.objects.filter(usersession__user_acc=user).delete()
# save current session
request.session.save()
# create a link from the user to the current session (for later removal)
UserSession.objects.get_or_create(
user_acc=user,
session_manager=Session.objects.get(pk=request.session.session_key)
)
I am not using a custom user model. When I run this code, it works properly when the user logs in the first time and creates an entry in the usersession table. After that I have problems:
If I login a second user, it logs them in, but it replaces the first user's session info with the second user's in the usersession table. I never see more than one entry in the usersession table, which is the last user to successfully login.
Worse, if I attempt to login the first user a second time (and this works even if there is no other user logged in). It removes the entry in the usersession table, but fails with an Update Error:
UpdateError at /
No exception message supplied
Once the entry has been deleted from the usersession table (as in 2 above), a user that is logged in can login to a new session...
I think it is the request.session.save() call that is causing the update error, but I'm at a loss, and I don't understand why I only get one entry in the usersession table when multiple accounts are successfully logged in. My understanding of how it should work is that the second login should successfully login, but the first login should be kicked out.
Any suggestions on what I am doing wrong, would be greatly appreciated.
Thanks--
Al
everyone. I hope you're doing well. I'm a Django newbie, trying to learn the basics of RESTful development. I only know Python, so this is my best fit for the moment.
Right now I'm trying to implement Unit tests for my API. It's a simple model to implement CRUD on the names and heights of NBA players. In my models I added a class to describe this data and translated it to a view with ModelViewSets. I wanted to make this data editable only for a specific type of user (a read-write user), only readable for another (read-only user) as well as unaccesible to non-authenticated users. To do so, I created a custom User Model and translated it to my views with a custom permission. Now I want to write a few Unit tests to check that:
r/w user can create a new player
r/w user can get a list of players
r/o user cannot create a new player
r/o user can get a list of players
unauthenticated user cannot create a new player
unauthenticated user cannot get a list of players
Here is my models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
ROLES = [('read-only', 'read-only'), ('read-write', 'read-write'),]
role = models.CharField(max_length=32, choices=ROLES, default='read-only')
# Create your models here.
class NBAplayers(models.Model):
first_name = models.CharField(max_length=100)
h_in = models.DecimalField(max_digits=5, decimal_places=2)
h_meters = models.DecimalField(max_digits=5, decimal_places=2)
last_name = models.CharField(max_length=120)
def __str__(self):
return self.first_name
And my views.py:
from .models import NBAplayers, User
from .serializers import NBAplayersSerializer
from rest_framework.response import Response
from rest_framework import status, viewsets, permissions
class ReadOnlyPermission(permissions.BasePermission):
def has_permission(self, request, view):
requests = ('POST', 'PUT', 'DELETE', 'PATCH')
user = request.user
role = User.role
if user.is_anonymous: # Not Authenticated
return request.method == 'GET'
else:
if role == 'read-write':
return request.method in requests + ('GET',)
else: # Read Only User
return request.method == 'GET'
class NBAPlayersViewSet(viewsets.ModelViewSet):
serializer_class = NBAplayersSerializer
queryset = NBAplayers.objects.all()
permission_classes = [ReadOnlyPermission]
And Finally, my urls.py:
from django.contrib import admin
from django.urls import path, include
from .views import NBAPlayersViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('players', NBAPlayersViewSet, basename = 'Players list')
urlpatterns = [
#djoser basic authentication
#Routers URLs
path('', include(router.urls)),
path('players/<int:pk>/', include(router.urls)),
path('', include('djoser.urls')),
path('', include('djoser.urls.authtoken')),
]
All the code above required to add the custom user to the global settings with AUTH_USER_MODEL = 'api_basic.User'. So, I've read the documentation and watched a few videos trying to understand how to write the proper tests, but the examples are not that close to this problem. I wanted to ask for a few pointers in the right direction, so that I can use it to build the rest of the tests. This is my first time writing unit tests.
Thank you beforehand for any help or input you can give. Cheers!
There's nothing tricky about this. For example, let's say I have a service that is only available to Admin/Staff users. It means you need to be both logged in and be an Admin.
In my test suite, I'll simply create a test_permissions method where I'll do something like:
Be logged out
Try the service, assert failure
Create normal user and log him in
Try the service, assert failure
Create admin user and log him in
Try the service, assert success
To give you a more concrete example, here's a snippet example with DRF. Do note that I'm using custom functions and 3rd party libraries to do some stuff, but the logic remains the same:
class TestCaseSuite(Base):
def test_permissions(self):
user = UserFactory()
admin = AdminFactory()
# 401 Not authenticated
self.api_client.logout()
response = self.api_client.post(url, data=data)
assert response.status_code == 401
# 403 Not admin
self.api_client.force_authenticate(user)
response = self.api_client.post(url, data=data)
assert response.status_code == 403
# 201 Admin
self.api_client.logout()
self.api_client.force_authenticate(admin)
response = self.api_client.post(url, data=data)
assert response.status_code == self.success_code
# ...
# other tests for that service like:
# testing custom fields
# testing a successful call and the response output
# testing side-effects, like when sending an email or whatnot
# etc
A few things to note:
In THIS test, I'm ONLY testing permissions. I'll use other tests for testing field validation, response output/format, etc.
If you find yourself needing a specific user for several tests, you could create it in the setUp method (which is triggered before EVERY test)
So, if you want to test specific permissions, you simply have to create the necessary objects/users and assert your expectations:
user with THIS data should fail
user with THAT data should succeed
etc.
I am using the middleware below to generate a list of currently logged-in users. The problem I am having is how to remove users automatically from the online_now and online_now_ids when they have logged out. I have tried to use signals but with no success...any help much appreciated
from django.core.cache import cache
from django.conf import settings
from django.contrib.auth.models import User
from django.utils.deprecation import MiddlewareMixin
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_out
#Set Environment variables for settings.py
ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 30*1)
ONLINE_MAX = getattr(settings, 'ONLINE_MAX', 50)
CACHE_MIDDLEWARE_SECONDS = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 10)
def get_online_now(self):
return User.objects.filter(id__in=self.online_now_ids or [])
class OnlineNowMiddleware(MiddlewareMixin):
"""
Maintains a list of users who logged into the website.
User ID's are available as `online_now_ids` on the request object,
and their corresponding users are available lazzily as the `online_now`
property on the request object
"""
def process_request(self, request):
#Get the index
uids = cache.get('online-now', [])
#multiget on individual uid keys
online_keys = ['online-%s' % (u,) for u in uids]
fresh = cache.get_many(online_keys).keys()
online_now_ids = [int(k.replace('online-','')) for k in fresh]
#if user is authenticated add id to list
if request.user.is_authenticated():
uid = request.user.id
#if uid in list bump to top
# and remove earlier entry
if uid in online_now_ids:
online_now_ids.remove(uid)
online_now_ids.append(uid)
if len(online_now_ids) > ONLINE_MAX:
del online_now_ids[0]
#Attach modifications to the request object
request.__class__.online_now_ids = online_now_ids
request.__class__.online_now = property(get_online_now)
#Set the new cache
cache.set('online-%s' % (request.user.pk), True, ONLINE_THRESHOLD)
cache.set('online-now', online_now_ids, ONLINE_THRESHOLD)
You can use sockets for this, otherwise it won't be REALLY showing online users.
Without sockets, it won't be so accurate but here is what you can do in steps:
Create a last activity (datetime) field for the user. (OneOnOne or any way you want to store this relationship)
In your middleware, change the code to update the last activity field of the user.
if request.user.is_authenticated():
user_activity, c = UserActivity.objects.get_or_create(user=request.user)
user_activity.last_activity = timezone.now()
user_activity.save()
That's all you need.
To query online users (not exact query, just an example):
User.objects.filter(activities__last_activity__gte=timezone.now() - timedelta(seconds=30))
I need to implement auditlog feature in one of my project which is using Django 1.8 and Django-Rest-Framework 3.2.2. I have extended BaseUserManager class to create user model since I had to use email as a username in my application ( if this information matters ).
Below is my db design which will hold logs :
**fields type desc**
id pk ( auto_increment)
cust_id FK customer
customer_name FK customer
user_id FK user
user_name FK user
module Varchar(100) sales,order,billing,etc
action Varchar(10) Create/Update/Delete
previous_value varchar(500)
current_value varchar(500)
Datetime Datetime timestamp of change
I have tried https://pypi.python.org/pypi/django-audit-log but it has 2 issues as per my requirement-
It does not capture data as per my requirement which I understand is my issue and so I modified it's code and added my fields into it's model.
It is not capturing module information. Behaviour is random.
I am seeking advice to proceed with this feature. Which package would be best suitable for my task.
P.S I have also tried Django-reversion and I have no requirement of data versioning.
Thanks
I achieved what I needed by modifying auditlog code -
Added required field in LogEntry model of auditlog.
Modified log_create,log_update,log_delete functions of receivers.py to save information in newly added fields.
Using this I am halfway done. Now only issue I am facing is that since model instance of 1 table contains information of other tables as well due to FKs used in the table.
To solve this I could come up with a solution which works well but I am not satisfied with it.
I added a function like include_in_model() in each model and modified auditlog's registry.py register() function to get those fields and only use that to save information in LogEntry model.
This approach will require me to create this include_in_model() function in each of my model class and pass required fields for particular model. This way I am avoiding FK related information.
Django Simple History is an excellent app that I've used in production projects in the past, it will give you per model Audits against your users.
Furthermore, you should create your own Authentication Class which will be responsible for logging requests. Let's assume that a User uses a Token to authenticate with your API. It gets sent in the header of each HTTP Request to your API like so: Authorization: Bearer <My Token>. We should then log the User associated with the request, the time, the user's IP and the body.
This is pretty easy:
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'common.authentication.MyTokenAuthenticationClass'
),
...
}
common/authentication.py
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions
from accounts.models import Token, AuditLog
class MyTokenAuthenticationClass(authentication.BaseAuthentication):
def authenticate(self, request):
# Grab the Athorization Header from the HTTP Request
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != b'bearer':
return None
# Check that Token header is properly formatted and present, raise errors if not
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
# Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
token.last_ip = get_real_ip(request)
token.last_login = timezone.now()
token.save()
# Add the saved token instance to the request context
request.token = token
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
# At this point, insert the Log into your AuditLog table:
AuditLog.objects.create(
user_id=token.user,
request_payload=request.body,
# Additional fields
...
)
# Return the Authenticated User associated with the Token
return (token.user, token)
Another solution would be to use django auditlog and use a custom middleware which does not capture the 'request.user' directly but at the moment when it is needed, by this time DRF will have set the correct 'request.user' so that it is no longer missing the username in the audit logs.
Create a file named (for example) auditlog_middleware.py and include it in the MIDDLEWARE in your settings.py instead of the default auditlog middleware.
from __future__ import unicode_literals
import threading
import time
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils.functional import curry
from django.apps import apps
from auditlog.models import LogEntry
from auditlog.compat import is_authenticated
# Use MiddlewareMixin when present (Django >= 1.10)
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object
threadlocal = threading.local()
class AuditlogMiddleware(MiddlewareMixin):
"""
Middleware to couple the request's user to log items. This is accomplished by currying the signal receiver with the
user from the request (or None if the user is not authenticated).
"""
def process_request(self, request):
"""
Gets the current user from the request and prepares and connects a signal receiver with the user already
attached to it.
"""
# Initialize thread local storage
threadlocal.auditlog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}
# In case of proxy, set 'original' address
if request.META.get('HTTP_X_FORWARDED_FOR'):
threadlocal.auditlog['remote_addr'] = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
# Connect signal for automatic logging
set_actor = curry(self.set_actor, request=request, signal_duid=threadlocal.auditlog['signal_duid'])
pre_save.connect(set_actor, sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'], weak=False)
def process_response(self, request, response):
"""
Disconnects the signal receiver to prevent it from staying active.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return response
def process_exception(self, request, exception):
"""
Disconnects the signal receiver to prevent it from staying active in case of an exception.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return None
#staticmethod
def set_actor(request, sender, instance, signal_duid, **kwargs):
"""
Signal receiver with an extra, required 'user' kwarg. This method becomes a real (valid) signal receiver when
it is curried with the actor.
"""
if hasattr(threadlocal, 'auditlog'):
if not hasattr(request, 'user') or not is_authenticated(request.user):
return
if signal_duid != threadlocal.auditlog['signal_duid']:
return
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
auth_user_model = apps.get_model(app_label, model_name)
except ValueError:
auth_user_model = apps.get_model('auth', 'user')
if sender == LogEntry and isinstance(request.user, auth_user_model) and instance.actor is None:
instance.actor = request.user
instance.remote_addr = threadlocal.auditlog['remote_addr']
I know that this answer is coming very late, but here it goes
Because DRF authenticates on the View level NOT on the Middleware level, the user is not yet attached to the request when AuditlogMiddleware runs, resulting in AnonymousUser
You can attach the logic from AuditlogMiddleware after your authentication
This logic connects some signals
This solution befits:
You don't have to decorate every View with it
it doesn't assume anything about AuditlogMiddleware or audit_log implementation in general. so if the code changes, this should still work
It doesn't force or duplicate DRF authentication.
#token_authentication_wrapper.py
from auditlog.middleware import AuditlogMiddleware
from rest_framework.authentication import TokenAuthentication
class TokenAuthenticationWrapper(TokenAuthentication):
def authenticate(self, request):
user, token = super().authenticate(request)
request.user = user # necessary for preventing recursion
AuditlogMiddleware().process_request(request)
return user, token
inherit from your favorite Authentication service e.g. BasicAuthentication SessionAuthentication, TokenAuthentication, etc...
and in setting.py
'DEFAULT_AUTHENTICATION_CLASSES': [
'path.to.file.token_authentication_wrapper.TokenAuthenticationWrapper',
]
First of all you can user package: https://github.com/jcugat/django-custom-user, to solve Email as Username field.
Then you can try to focus development with: http://django-reversion.readthedocs.io/en/stable/
The answer by #hassaan-alansary would have been ideal, but unfortunately the Auditlog devs made significant changes since he posted his answer, and I couldn't figure out how to reconcile their changes with Hassaan's answer.
The solution I ended up finding is based on what was shared here. Instead of writing a new DRF authentication method which invokes the middleware to do the logging, it creates a mixin which needs to be added to each of the DRF views you want added to the audit log. The solution below is the modified version of the one I ended up using from the link above.
# mixins.py
import threading
import time
from functools import partial
from django.db.models.signals import pre_save
from auditlog.models import LogEntry
threadlocal = threading.local()
class DRFDjangoAuditModelMixin:
"""
Mixin to integrate django-auditlog with Django Rest Framework.
This is needed because DRF does not perform the authentication at middleware layer
instead it performs the authentication at View layer.
This mixin adds behavior to connect/disconnect the signals needed by django-auditlog to auto
log changes on models.
It assumes that AuditlogMiddleware is activated in settings.MIDDLEWARE_CLASSES
"""
#staticmethod
def _set_actor(user, sender, instance, signal_duid, **kwargs):
# This is a reimplementation of auditlog.context._set_actor.
# Unfortunately the original logic cannot be used, because
# there is a type mismatch between user and auth_user_model.
if signal_duid != threadlocal.auditlog["signal_duid"]:
return
if (
sender == LogEntry
#and isinstance(user, auth_user_model)
and instance.actor is None
):
instance.actor = user
instance.remote_addr = threadlocal.auditlog["remote_addr"]
def initial(self, request, *args, **kwargs):
"""Overwritten to use django-auditlog if needed."""
super().initial(request, *args, **kwargs)
remote_addr = AuditlogMiddleware._get_remote_addr(request)
actor = request.user
set_actor = partial(
self._set_actor,
user=actor,
signal_duid=threadlocal.auditlog["signal_duid"],
)
pre_save.connect(
set_actor,
sender=LogEntry,
dispatch_uid=threadlocal.auditlog["signal_duid"],
weak=False,
)
def finalize_response(self, request, response, *args, **kwargs):
"""Overwritten to cleanup django-auditlog if needed."""
response = super().finalize_response(request, response, *args, **kwargs)
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
del threadlocal.auditlog
return response
You then need to add this mixin to each of your views:
# views.py
...
class CustomerViewSet(DRFDjangoAuditModelMixin, ModelViewSet):
queryset = Client.objects.all()
serializer = ClientSerializer
....
The down side of this implementation is that it isn't DRY on a couple of levels. Not only do you need to add the mixin to each DRF view, but it copies code from nearly all the logging behaviour of auditlog, particularly private methods. I therefore expect this solution to either need adjustment in the future, or for it to also become obsolete.
The solution above is based on this revision of auditlog.
As per title, in Django, when and where i should store information about the user access? I'm already logging the user login/logout using Django built-in signals, but what if the user is already logged on that machine? How can i detect this?
I wanted to do the same thing on my Django project. The problem is that the last_login variable doesn't helps since it is updated at the user login, and If the user cache his session the variable will never be updated.
One solution is to create a decorator, and use it on your view's. In that way if the user uses your site, the variable will get updated.
This is an example of how to do it:
from functools import wraps
from django.contrib.auth.models import User
from django.utils import timezone
def W_update_user_login(func):
#wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if request.user.is_authenticated():
user=User.objects.get(username=request.user)
user.last_login=timezone.now()
user.save()
return func(*args, **kwargs)
return wrapper
If you add the previous code in to your views.py file, you will be able to decorate your functions. ex:
#W_update_user_login
def your_view_function(request):
#
# your normal code..
#
return HttpResponse....