Ticket #3583: apache_auth.5.patch

File apache_auth.5.patch, 10.7 KB (added by Rick van Hattem <Rick.van.Hattem@…>, 7 years ago)

Patch for django/contrib/auth/handlers/modpython.py@7403

  • 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     # apache 2.2 requires a call to req.get_basic_auth_pw() before
    14     # req.user and friends are available.
    15     req.get_basic_auth_pw()
     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
    1638
    17     # check for PythonOptions
    18     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
     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
    1952
    20     options = req.get_options()
    21     permission_name = options.get('DjangoPermissionName', None)
    22     staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
    23     superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
    24     settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
    25     if settings_module:
    26         os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
     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     from django.contrib.auth.models import User
    29     from django import db
    30     db.reset_queries()
     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)
    3175
    32     # check that the username is valid
    33     kwargs = {'username': req.user, 'is_active': True}
    34     if staff_only:
    35         kwargs['is_staff'] = True
    36     if superuser_only:
    37         kwargs['is_superuser'] = True
     76    dispatcher.send(signal=signals.request_started)
    3877    try:
    39         try:
    40             user = User.objects.get(**kwargs)
    41         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.
    4287            return apache.HTTP_UNAUTHORIZED
    43    
    44         # check the password and any permission given
    45         if user.check_password(req.get_basic_auth_pw()):
    46             if permission_name:
    47                 if user.has_perm(permission_name):
    48                     return apache.OK
    49                 else:
    50                     return apache.HTTP_UNAUTHORIZED
    51             else:
    52                 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
    5399        else:
    54             return apache.HTTP_UNAUTHORIZED
     100            return apache.HTTP_UNAUTHORIZED   
    55101    finally:
    56         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
     
    7284            PythonAuthenHandler django.contrib.auth.handlers.modpython
    7385        </Location>
    7486
    75 By default, the authentication handler will limit access to the ``/example/``
    76 location to users marked as staff members.  You can use a set of
     87By default, the authentication handler examples above will limit access to the
     88``/example/`` location to users marked as staff members.  You can use a set of
    7789``PythonOption`` directives to modify this behavior:
    7890
    7991    ================================  =========================================
     
    97109
    98110                                      By default no specific permission will be
    99111                                      required.
     112
     113    ``DjangoRaiseForbidden``          If the user authenticates but does not
     114                                      have the valid credentials, raise HTTP
     115                                      Error 403 (forbidden) rather providing a
     116                                      login prompt again.
     117
     118                                      Defaults to ``off``.
    100119    ================================  =========================================
    101120
     121You may also want to make Apache pass the correct headers to stop proxy servers
     122(and local browser caches) from caching your protected resources. Assuming that
     123the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache
     124server, you can add the following directives to your ``Location`` block::
     125
     126    # Stop resources from being cached
     127    ExpiresActive On
     128    ExpiresDefault A0
     129    Header append Cache-Control: "no-cache, no-store, must-revalidate, private"
     130
    102131Note that sometimes ``SetEnv`` doesn't play well in this mod_python
    103132configuration, for reasons unknown. If you're having problems getting
    104133mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using
Back to Top