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.