Things I Learned About Django Authentication and django_cas Today

After programming a new site on my laptop (standalone web server, Django standard authentication), I moved it to the DEV server (Apache, CAS for authentication). Some things didn’t work the same.

I had designed three security settings: Admin (can get to everything – superuser in Admin), Staff (can run reports, just a few permissions in Admin – marked as staff), and User (entry in Admin, but not staff and no permission, runs reports only).

The views that displayed the reports use the @login_required decorator.

Here’s the problem, and I saw this coming: any user who could sucessfully log in to CAS could run reports. Since I’m using a campus wide CAS database, this included any campus staff member or student. Not what I was going for.

I had forgotten that django_cas adds every sucessfully loged in user as a User record in the Django Admin. At that point, the user passes the @login_required test and can run those views.

My solution was two-fold. Mark the User level records as staff, and replace the @login_required decorator with @staff_member_required. Unfortunately, the system seemed to be using the Django internal login mechanism rather than CAS. A little code inspection showed that the decorator wasn’t using a URL redirect to login, and instead was calling the Admin login function directly. This bypasses both the django_cas middleware and the LOGIN_URL setting.

Next, I tried using the @user_passes_test decorator testing for user.is_staff in place of @staff_member_required (which I will likely never use again). This also worked in the standalone setting, but not with django_cas (kept throwing 500 errors). I then switched to the versions of the @user_passes_test and @login_required decorators provided with django_cas, but then I kept getting 403 errors.

TIME FOR A LUNCH BREAK!!

After a sandwich and a quick walk, my refreshed mind reminded me to check to see if django_cas had been updated from v2.0.2 that I had installed. Sure enough! Version 2.0.3 specifically updated the decorators. The @user_passes_test code had been updated to return a 403 only if a logged in user failed the test, and an unauthenticated user would get a proper login screen. Furthermore, @login_required had been removed completely and replaced with an import of the standard django supplied version. Even though I am currently unable to update the version on the DEV (or higher) servers, I saw what I needed to do.

First, I changed my import statements to grab the correct decorators for the environment.

import settings

if settings.USE_CAS:
    from django_cas.decorators import user_passes_test
else:
    from django.contrib.auth.decorators import user_passes_test
    
from django.contrib.auth.decorators import login_required

(Note that USE_CAS is a setting I’ve defined in the server specific settings file.)

This code pulls in the proper @user_passes_test decorator, and the standard @login_required decorator,

Next, for each veiw that needs protection, I implemented the decorators thusly:

@login_required   
@user_passes_test(lambda u: u.is_staff)
def view_staff(request):
    ...

Putting @login_required first forces a login if the user hasn’t, and then @user_passes_test checks for the staff attribute.

With the changes in django_cas v2.0.3, the call to @login_required will no longer be necessary. I hope to setup a project to update the version soon.