Ticket #3583: apache_auth.4.patch

File apache_auth.4.patch, 10.6 KB (added by SmileyChris, 8 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
Back to Top