Code

Ticket #3583: apache_auth.2.patch

File apache_auth.2.patch, 7.5 KB (added by SmileyChris, 7 years ago)

HTTP_FORBIDDEN if user authenticates but does not have correct permissions

  • django/contrib/auth/handlers/modpython.py

     
    11from mod_python import apache 
    22import os 
     3from django.core import signals 
     4from django.dispatch import dispatcher 
     5from django.core.handlers.base import BaseHandler 
     6from django.core.handlers.modpython import ModPythonRequest 
     7from django.contrib.auth import authenticate 
    38 
     9_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     10 
     11class ModPythonAuthOptions: 
     12    def __init__(self, req): 
     13        options = req.get_options() 
     14        self.permission_name = options.get('DjangoPermissionName', None) 
     15        self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) 
     16        self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) 
     17        self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None) 
     18 
     19def setup_environment(req, options): 
     20    """ 
     21    mod_python fakes the environ, and thus doesn't process SetEnv. This ensures 
     22    any future imports relying on settings will work. 
     23    """ 
     24    os.environ.update(req.subprocess_env) 
     25    if options.settings_module: 
     26        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module 
     27 
     28def validate_user(user, options): 
     29    if not user: 
     30        return False 
     31    # Don't require an is_authenticated property, but if it's there then check it 
     32    if hasattr(user, 'is_authenticated') and not user.is_authenticated(): 
     33        return False 
     34    # Don't require an is_active property, but if it's there then check it 
     35    if hasattr(user, 'is_active') and not user.is_active: 
     36        return False 
     37    if options.staff_only and not getattr(user, 'is_staff', None): 
     38        return False 
     39    if options.superuser_only and not getattr(user, 'is_superuser', None): 
     40        return False 
     41    # If a permission is required then user must have a has_perm function to validate 
     42    if options.permission_name and (not hasattr(user, 'has_perm') or not user.has_perm(self.permission_name)): 
     43        return False 
     44    return True 
     45 
    446def authenhandler(req, **kwargs): 
    547    """ 
    6     Authentication handler that checks against Django's auth database. 
     48    mod_python authentication handler that checks against Django's auth 
     49    database. 
    750    """ 
     51    options = ModPythonAuthOptions(req) 
     52    setup_environment(req, options) 
    853 
    9     # mod_python fakes the environ, and thus doesn't process SetEnv.  This fixes 
    10     # that so that the following import works 
    11     os.environ.update(req.subprocess_env) 
     54    dispatcher.send(signal=signals.request_started) 
     55    try: 
     56        # This populates req.user too, so it's important to do first 
     57        password = req.get_basic_auth_pw() 
    1258 
    13     # check for PythonOptions 
    14     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     59        # Get the user from any of the installed backends 
     60        user = authenticate(username=req.user, password=password) 
    1561 
    16     options = req.get_options() 
    17     permission_name = options.get('DjangoPermissionName', None) 
    18     staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) 
    19     superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) 
    20     settings_module = options.get('DJANGO_SETTINGS_MODULE', None) 
    21     if settings_module: 
    22         os.environ['DJANGO_SETTINGS_MODULE'] = settings_module 
     62        # Raise unauthorized if the user wasn't authenticated to bring up 
     63        # a password dialog box to allow the user to authenticate. 
     64        if not user: 
     65            return apache.HTTP_UNAUTHORIZED 
    2366 
    24     from django.contrib.auth.models import User 
    25     from django import db 
    26     db.reset_queries() 
     67        # Validate the user 
     68        if validate_user(user, options): 
     69            return apache.OK 
     70        else: 
     71            # mod_python docs say that HTTP_FORBIDDEN should be raised if the 
     72            # user authenticates but doesn't validate. 
     73            return apache.HTTP_FORBIDDEN 
     74    finally: 
     75        dispatcher.send(signal=signals.request_finished) 
    2776 
    28     # check that the username is valid 
    29     kwargs = {'username': req.user, 'is_active': True} 
    30     if staff_only: 
    31         kwargs['is_staff'] = True 
    32     if superuser_only: 
    33         kwargs['is_superuser'] = True 
     77def accesshandler(req): 
     78    """ 
     79    mod_python access handler that uses the contrib.auth framework (with 
     80    sessions and therefore requiring a session cookie). 
     81    """ 
     82    options = ModPythonAuthOptions(req) 
     83    setup_environment(req, options) 
     84 
     85    # Set up middleware, now that settings works we can do it now. 
     86    base_handler = BaseHandler() 
     87    base_handler.load_middleware() 
     88 
     89    dispatcher.send(signal=signals.request_started) 
    3490    try: 
    35         try: 
    36             user = User.objects.get(**kwargs) 
    37         except User.DoesNotExist: 
    38             return apache.HTTP_UNAUTHORIZED 
    39      
    40         # check the password and any permission given 
    41         if user.check_password(req.get_basic_auth_pw()): 
    42             if permission_name: 
    43                 if user.has_perm(permission_name): 
    44                     return apache.OK 
    45                 else: 
    46                     return apache.HTTP_UNAUTHORIZED 
    47             else: 
    48                 return apache.OK 
     91        request = ModPythonRequest(req) 
     92 
     93        # Apply request middleware 
     94        for middleware_method in base_handler._request_middleware: 
     95            response = middleware_method(request) 
     96            if response: 
     97                # If we get a response, we should probably stop processing any 
     98                # remaining request middleware. 
     99                break 
     100 
     101        # Validate the user 
     102        user = getattr(request, 'user', None) 
     103        if validate_user(user, options): 
     104            return apache.OK 
    49105        else: 
    50             return apache.HTTP_UNAUTHORIZED 
     106            return apache.HTTP_FORBIDDEN 
    51107    finally: 
    52         db.connection.close() 
     108        dispatcher.send(signal=signals.request_finished) 
  • docs/apache_auth.txt

     
    1717================== 
    1818 
    1919To check against Django's authorization database from a Apache configuration 
    20 file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along 
     20file, you can either use mod_python's ``PythonAuthenHandler`` directive along 
    2121with the standard ``Auth*`` and ``Require`` directives:: 
    2222 
    2323    <Location /example/> 
     
    2929        PythonAuthenHandler django.contrib.auth.handlers.modpython 
    3030    </Location> 
    3131 
     32... or use mod_python's ``PythonAccessHandler`` directive:: 
     33 
     34    <Location /example/> 
     35        SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
     36        PythonAccessHandler django.contrib.auth.handlers.modpython 
     37    </Location> 
     38 
     39The difference between these two methods is that ``PythonAuthenHandler`` uses 
     40basic HTTP authentication where as ``PythonAccessHandler`` uses the built-in 
     41``contrib.auth`` authentication (which uses a session cookie). 
     42 
     43``PythonAuthenHandler`` will prompt the user to enter their user name and 
     44password (usually via a basic authentication dialog box), while 
     45``PythonAccessHandler`` will simply raise an Apache "Forbidden" error if the 
     46user is not logged in or does not have the correct authorization. 
     47 
    3248By default, the authentication handler will limit access to the ``/example/`` 
    3349location to users marked as staff members.  You can use a set of 
    3450``PythonOption`` directives to modify this behavior: