Code

Ticket #3583: apache_auth.3.patch

File apache_auth.3.patch, 10.6 KB (added by SmileyChris, 7 years ago)
  • django/contrib/auth/handlers/modpython.py

     
    11from mod_python import apache 
    22import os 
     3from urllib import quote 
     4from django.core import signals 
     5from django.dispatch import dispatcher 
     6from django.core.handlers.base import BaseHandler 
     7from django.core.handlers.modpython import ModPythonRequest 
     8from django.conf import settings 
     9from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME 
     10from django.utils.encoding import iri_to_uri 
    311 
    4 def authenhandler(req, **kwargs): 
     12_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     13 
     14class ModPythonAuthOptions: 
     15    def __init__(self, req): 
     16        options = req.get_options() 
     17        self.permission_name = options.get('DjangoPermissionName', None) 
     18        self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) 
     19        self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) 
     20        self.raise_forbidden = _str_to_bool(options.get('DjangoRaiseForbidden', "off")) 
     21        self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None) 
     22 
     23def setup_environment(req, options): 
    524    """ 
    6     Authentication handler that checks against Django's auth database. 
     25    mod_python fakes the environ, and thus doesn't process SetEnv. This ensures 
     26    any future imports relying on settings will work. 
    727    """ 
    8  
    9     # mod_python fakes the environ, and thus doesn't process SetEnv.  This fixes 
    10     # that so that the following import works 
    1128    os.environ.update(req.subprocess_env) 
     29    if options.settings_module: 
     30        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module 
    1231 
    13     # check for PythonOptions 
    14     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     32def authenticate_user(user): 
     33    if user is None: 
     34        return False 
     35    if hasattr(user, 'is_authenticated') and not user.is_authenticated(): 
     36        return False 
     37    return True 
    1538 
    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 
     39def validate_user(user, options): 
     40    if hasattr(user, 'is_active') and not user.is_active: 
     41        return False 
     42    if options.staff_only and not getattr(user, 'is_staff', None): 
     43        return False 
     44    if options.superuser_only and not getattr(user, 'is_superuser', None): 
     45        return False 
     46    # If a permission is required then user must have a has_perm function to 
     47    # validate. 
     48    if options.permission_name and (not hasattr(user, 'has_perm') or 
     49                                    not user.has_perm(options.permission_name)): 
     50        return False 
     51    return True 
    2352 
    24     from django.contrib.auth.models import User 
    25     from django import db 
    26     db.reset_queries() 
     53def redirect_to_login(req): 
     54    path = quote(req.uri) 
     55    if req.args: 
     56        path = '%s?%s' % (path, req.args) 
     57    path = quote(path) 
     58    iri = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path) 
     59    uri = iri_to_uri(iri) 
     60    req.err_headers_out.add('Location', uri) 
     61    if req.proto_num >= 1001: 
     62        # Use HTTP Error 303 (see other) for HTTP/1.1 browsers. 
     63        raise apache.SERVER_RETURN, apache.HTTP_SEE_OTHER 
     64    else: 
     65        # Otherwise use HTTP Error 302 (moved temporarily). 
     66        raise apache.SERVER_RETURN, apache.HTTP_MOVED_TEMPORARILY 
    2767 
    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 
     68def authenhandler(req, **kwargs): 
     69    """ 
     70    mod_python authentication handler that checks against Django's auth 
     71    database. 
     72    """ 
     73    options = ModPythonAuthOptions(req) 
     74    setup_environment(req, options) 
     75 
     76    dispatcher.send(signal=signals.request_started) 
    3477    try: 
    35         try: 
    36             user = User.objects.get(**kwargs) 
    37         except User.DoesNotExist: 
     78        # This populates req.user too, so it's important to do first. 
     79        password = req.get_basic_auth_pw() 
     80 
     81        # Get the user from any of the installed backends. 
     82        user = authenticate(username=req.user, password=password) 
     83 
     84        if not authenticate_user(user): 
     85            # Raise unauthorized if the user doesn't authenticate to bring up a 
     86            # password dialog box to allow the user to authenticate. 
    3887            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 
     88 
     89        # Validate the user 
     90        if validate_user(user, options): 
     91            return apache.OK 
     92 
     93        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user 
     94        # authenticates but doesn't validate but Django provides it as an 
     95        # option, alternately raising HTTP_UNAUTHORIZED again to provide the 
     96        # option of logging in as an alternate user. 
     97        if options.raise_forbidden: 
     98            return apache.HTTP_FORBIDDEN 
    4999        else: 
    50             return apache.HTTP_UNAUTHORIZED 
     100            return apache.HTTP_UNAUTHORIZED     
    51101    finally: 
    52         db.connection.close() 
     102        dispatcher.send(signal=signals.request_finished) 
     103 
     104def accesshandler(req): 
     105    """ 
     106    mod_python access handler that uses the contrib.auth framework (with 
     107    sessions, therefore requiring a session cookie). 
     108    """ 
     109    options = ModPythonAuthOptions(req) 
     110    setup_environment(req, options) 
     111 
     112    # Set up middleware, now that settings works we can do it now. 
     113    base_handler = BaseHandler() 
     114    base_handler.load_middleware() 
     115 
     116    dispatcher.send(signal=signals.request_started) 
     117    try: 
     118        request = ModPythonRequest(req) 
     119 
     120        # Apply request middleware 
     121        for middleware_method in base_handler._request_middleware: 
     122            response = middleware_method(request) 
     123            if response: 
     124                # If we get a response then there's no need to keep processing 
     125                # any remaining request middleware. 
     126                break 
     127 
     128        user = getattr(request, 'user', None) 
     129        if not authenticate_user(user): 
     130            # Rather than raising HTTP_UNAUTHORIZED (which the browser won't be 
     131            # able to handle since this isn't basic HTTP authentication), write 
     132            # a response which redirects to settings.LOGIN_URL 
     133            redirect_to_login(req) 
     134 
     135        if validate_user(user, options): 
     136            return apache.OK 
     137 
     138        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user 
     139        # authenticates but doesn't validate but Django provides it as an 
     140        # option, alternately redirecting to login to provide the option of 
     141        # logging in as an alternate user. 
     142        if options.raise_forbidden: 
     143            return apache.HTTP_FORBIDDEN 
     144        else: 
     145            redirect_to_login(req) 
     146 
     147    finally: 
     148        dispatcher.send(signal=signals.request_finished) 
  • docs/apache_auth.txt

     
    1616Configuring Apache 
    1717================== 
    1818 
    19 To check against Django's authorization database from a Apache configuration 
    20 file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along 
    21 with the standard ``Auth*`` and ``Require`` directives:: 
     19To check against Django's authorization database from an Apache configuration 
     20file, you can either use mod_python's ``PythonAccessHandler`` directive or  
     21the ``PythonAuthenHandler`` directive along with the standard ``Auth*`` and 
     22``Require`` directives. 
    2223 
     24The ``PythonAccessHandler`` directive validates using the built-in 
     25``contrib.auth`` authentication, which uses the session cookie and redirects to 
     26the ``settings.LOGIN`` URL if authentication is required:: 
     27 
    2328    <Location /example/> 
     29        SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
     30        PythonAccessHandler django.contrib.auth.handlers.modpython 
     31    </Location> 
     32 
     33The ``PythonAuthenHandler`` directive just uses basic HTTP authentication:: 
     34 
     35    <Location /example/> 
    2436        AuthType basic 
    2537        AuthName "example.com" 
    2638        Require valid-user 
     
    2941        PythonAuthenHandler django.contrib.auth.handlers.modpython 
    3042    </Location> 
    3143 
    32 By default, the authentication handler will limit access to the ``/example/`` 
    33 location to users marked as staff members.  You can use a set of 
     44By default, the authentication handler examples above will limit access to the 
     45``/example/`` location to users marked as staff members.  You can use a set of 
    3446``PythonOption`` directives to modify this behavior: 
    3547 
    3648    ================================  ========================================= 
     
    5466 
    5567                                      By default no specific permission will be 
    5668                                      required. 
     69 
     70    ``DjangoRaiseForbidden``          If the user authenticates but does not 
     71                                      have the valid credentials, raise HTTP 
     72                                      Error 403 (forbidden) rather providing a 
     73                                      login prompt again. 
     74 
     75                                      Defaults to ``off``. 
    5776    ================================  ========================================= 
    5877 
     78You may also want to make Apache pass the correct headers to stop proxy servers 
     79(and local browser caches) from caching your protected resources. Assuming that 
     80the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache 
     81server, you can add the following directives to your ``Location`` block:: 
     82 
     83    # Stop resources from being cached 
     84    ExpiresActive On 
     85    ExpiresDefault A0 
     86    Header append Cache-Control: "no-cache, no-store, must-revalidate, private" 
     87 
    5988Note that sometimes ``SetEnv`` doesn't play well in this mod_python 
    6089configuration, for reasons unknown. If you're having problems getting 
    6190mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using