Commit 2fac1a25 by Chris Green

added README and workarounds for the @permission_required infinite loop

parent e36a262b
Django CAS:
Original Author: Brian Beck <exogen@gmail.com>
Current Maintainer/User: Chris Green <greencm@gmail.com>
Django CAS authentication module for a while, I decided to make a couple improvements.
The biggest improvement is that instead of modifying code in the CAS
module itself to set your CAS address and do things like custom User
field population, all this stuff can now be configured in your
settings file.
Another improvement is that CAS authentication now works for the
bundled admin interface. Since the administration interface does not
account for an authentication backend that doesn't know the user's
password, this makes the login form useless. The CAS module will now
intercept requests to the administration interface and do the proper
authentication routine if necessary, never showing the login form
(which doesn't make sense for CAS). Intercepting requests, you ask?
Yes, that means the CAS module is now middleware. Actually it's
middleware, a couple views, and an authentication backend. This
authentication backend actually just augments the built-in
authentication backend so both must be enabled even though credentials
from the built-in contrib.auth backend will fail!
Place the django_cas directory in your PYTHONPATH.
Now add it to the middleware and authentication backends in your settings. Make sure you also have the authentication middleware installed. Here's what mine looks like:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_cas.cas.middleware.CASMiddleware',
'django.middleware.doc.XViewMiddleware',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django.contrib.cas.backend.CASBackend',
)
You can now configure the CAS module in the same settings file. Here are the possible options, most of which can be safely ignored:
* CAS_SERVER_URL: This is the only setting you must explicitly define. Set it to the base URL of your CAS source.
* CAS_POPULATE_USER: A callable or the location of a callable. When a user logs in and is missing name and email attributes in the database, this will be called with their User model instance. Default is None (do nothing).
* CAS_ADMIN_PREFIX: The URL prefix of the Django administration site. If undefined, the CAS middleware will just check the view being rendered to see if it lives in django.contrib.admin.views. The method is a little evil, but it works.
* CAS_LOGIN_URL: The URL where you bound django.contrib.cas.views.login. If undefined, assume /accounts/login/.
* CAS_LOGOUT_URL: The URL where you bound django.contrib.cas.views.logout. If undefined, assume /accounts/logout/.
* CAS_REDIRECT_URL: Where to send a user after logging in or out if there is no referrer and no next page set. Default is /.
* CAS_REDIRECT_FIELD_NAME: The name of the GET parameter in which to store the page URL to send the user to after logging in. Default is next.
Need an example? Here's what my CAS settings look like:
CAS_SERVER_URL = 'https://login.example.edu/cas/'
CAS_POPULATE_USER = 'present.utils.populate_user'
And the callable that lives at present.utils.populate_user (notice this code lives in my project instead of tinkering with the CAS module) looks like this:
def populate_user(user):
try:
ldap = LDAP()
person = ldap.filter_one_by(uid=user.username)
except:
if not user.email:
user.email = "%s@case.edu" % user.username
else:
# If it succeeds, update their User entry
user.email = person.mail[0]
user.first_name = fix_case(person.givenName[0])
user.last_name = fix_case(person.sn[0])
(LDAP and fix_case also live in my utils module).
Finally, make sure your project knows how to log users in and out by adding these to your URLconf:
(r'^accounts/login/$', 'django.contrib.cas.views.login'),
(r'^accounts/logout/$', 'django.contrib.cas.views.logout'),
Users should now be able to log into your site, and staff into the administration interface, using CAS 1.0.
If you require the use of the @permission_required decorator, there is
a known issue that can cause an infinite loop. You can look at
applying the django/contrib/auth/decorators.py to integrate the two.
There is also a permfail.py that worked with older 0.96 versions of
the django framework.
For more information, see
http://groups.google.com/group/django-developers/browse_thread/thread/8f81ed9644dfc84e
Index: /usr/local/src/django/django/contrib/auth/decorators.py
===================================================================
--- /usr/local/src/django/django/contrib/auth/decorators.py (revision 6941)
+++ /usr/local/src/django/django/contrib/auth/decorators.py (working copy)
@@ -1,5 +1,5 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
-from django.http import HttpResponseRedirect
+from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.utils.http import urlquote
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
@@ -30,6 +30,7 @@
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
"""
+ # import pdb; pdb.set_trace()
return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
class _CheckLogin(object):
@@ -58,8 +59,19 @@
return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name)
def __call__(self, request, *args, **kwargs):
+ """ Execute the test_function for the end user,
+ otherwise, redirect them to an appropriate page """
+
if self.test_func(request.user):
return self.view_func(request, *args, **kwargs)
+
path = urlquote(request.get_full_path())
+
+ if request.user.is_authenticated():
+ # pushing the user back through the login_url only makes
+ # sense if they haven't already done that.
+ return HttpResponseForbidden("<h1>Access Forbidden: You do not have rights to %s</h1>" % path)
+
tup = self.login_url, self.redirect_field_name, path
+
return HttpResponseRedirect('%s?%s=%s' % tup)
# PERMFAIL Modifications
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect, HttpResponseForbidden
from urllib import quote
from django.conf import settings
FAIL_URL = "/oops"
def authenticated_user_passes_test(test_func, login_url=settings.LOGIN_URL, fail_url=FAIL_URL):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
The failure url is for when people need to pass a test but needs a
response other than being redirected to the login page.
The auth_required option is there in case your test_function performs
"""
def _dec(view_func):
def _checklogin(request, *args, **kwargs):
# import pdb; pdb.set_trace()
if not request.user.is_authenticated():
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME,
quote(request.get_full_path())))
if test_func(request.user):
return view_func(request, *args, **kwargs)
# We have failed, give us the error page
error = "<h1>Forbidden</h1><p>You do not have sufficient permissions<p>"
return HttpResponseForbidden(error)
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
def permission_required(perm, login_url=settings.LOGIN_URL, fail_url=FAIL_URL):
"""
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
"""
return authenticated_user_passes_test(lambda u: u.has_perm(perm), login_url=login_url,fail_url=fail_url)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment