I want to log when session hash verification fails. The logging code should be inserted inside this https://github.com/django/django/blob/master/django/contrib/auth/init.py#L183 if block.
I am trying to figure out what would be the best way to implement this. Currently it looks like I will need to override the whole django.contrib.auth.middleware.AuthenticationMiddleware.
Do you have any tips for me?
Why don't you copy get_user function and put the logger like you want to:
from django.contrib.auth import *
def your_get_user(request):
"""
Returns the user model instance associated with the given request session.
If no user is retrieved an instance of `AnonymousUser` is returned.
"""
from django.contrib.auth.models import User, AnonymousUser
user = None
try:
user_id = _get_user_session_key(request)
backend_path = request.session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if ('django.contrib.auth.middleware.SessionAuthenticationMiddleware'
in settings.MIDDLEWARE_CLASSES and hasattr(user, 'get_session_auth_hash')):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
log = logging.getLogger("YourLog")
log.debug(session_hash)
request.session.flush()
user = None
return user or AnonymousUser()
And use this like you want to in your code
Related
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'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 am trying to test the admin changelist views if they work properly. This is the code that I use:
from django.test import Client
from django.test import TestCase
class TestAdminViews(TestCase):
def test_admin(self, user = None, name = None):
client=Client()
if user is None:
password = "test"
user = User.objects.create_superuser('testadmin', 'test#test.com', password)
success=client.login(username = user.username, password = password)
self.assertTrue(success, "Login Failed!")
app="messwertdb"
mymodels=["messrun","messung"]
for model in mymodels:
response = client.get(reverse('admin:%s_%s_changelist'% (app, model)))
print reverse('admin:%s_%s_changelist'% (app, model)), response.status_code, response.content.count("exception")#test line self.assertEqual(response.status_code,200,"Response not ok!")
I have broken one of the views by trying to display a nonexisting attribute. So if I try to get it in the browser I get an AttributeError (which results in a 500 response if DEBUG=False). However, in my test I always get a 200 response -meaning no testfailures. If I enter the exact same code in the shell I get the proper Error. In the test case it seems to return an empty but otherwise correct changelist.
The broken admin.py:
class MessRunAdmin(admin.ModelAdmin):
list_display=('type','date','path','failure')
def failure(self, obj):
return obj.nonexisting #this does not exist
It was a simple mistake: I forgot to load the fixture so the testdatabase was empty and as it did not try to show any instance it did not call the failing attribute. And in the shell it was using the deployment database which was not empty.
So I changed it to the following code, which now fails:
class TestAdminViews(TestCase):
fixtures= ["messwertdb.yaml"]
def test_admin(self, user = None, name = None):
.......
I implemented my own User class from scratch in Django. But when I log in I have this error:
The following fields do not exist in this model or are m2m fields: last_login
I really don't want the field last_login.
I do some reasearch and the problem is here: contrib.aut.models.py
def update_last_login(sender, user, **kwargs):
"""
A signal receiver which updates the last_login date for
the user logging in.
"""
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
user_logged_in.connect(update_last_login)
I found a workaround but it's not an ellegant solution. I added user_logged_in.disconnect(update_last_login) in my models.py file, where my User class is defined.
Is there any better solution for this?
Not sure if this is related to a newer version of django or what, but in my case
user_logged_in.disconnect(update_last_login)
didn't work. This is what works for me (django 2.1):
user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
Currently in Django 1.7...
I think the workaround you defined is the only valid solution (besides from a monkey patch) currently when using the Django auth login() method. I'm just going to assume you are using the standard login() method which is raising this exception.
If we take a look at the source for the login method, we find at the end of the method, a call to execute user_logged_in.send(sender=user.__class__, request=request, user=user). We can't prevent this signal from executing besides from disconnecting it as you have pointed out.
Alternatively, we could monkey patch the login() method to remove that signal call.
from django.contrib.auth import login
def monkey_patch_login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ''
if user is None:
user = request.user
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
request.session.get(HASH_SESSION_KEY) != session_auth_hash):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = user.backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
login = monkey_patch_login
We would put the monkey patch code at the top of the file that needs to call the login() method.
I have a question about how the code run in inheritance in Python. It might look like a dummy question somehow, but I a new to Python.
This a code snippet from some Facebook application I am working on:
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
except Exception, ex:
self.log_exception(ex)
raise
def init_facebook(self):
"""Sets up the request specific Facebook and user instance"""
facebook = Facebook()
user = None
# Initially Facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# We reset the method to GET because a request from Facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. In a web application this causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# Try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# Update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# Refresh data if we failed in doing so after a realtime ping.
if user.dirty:
user.refresh_data()
# Restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # Ignore if can't get the minimum fields.
self.facebook = facebook
self.user = user
This is another class that inherits from BaseHandler
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
Does the initialize function in the BaseHandler get called when the RecentRunsHandler is called?
I am asking this because if after the user "allow" the application (and the user data is saved in the database) ... The application still redirects him to the welcoming page where the Facebook-login button exists.
To make it more clear, the application can't know that the user has authorized him before.
Probably not. Perhaps you should def initialize to def __init__? Python objects get instantiated through the __init__ method, not through any initialize() method. Since you don't seem to have any explicit calls to initialize(), it will probably not be called.