Ticket #3583: apach_auth.7.patch

File apach_auth.7.patch, 10.5 KB (added by peterd12, 16 years ago)

Updated to work with refactored django.dispatch. Applies cleanly against rev 8630.

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

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

     
    2222Configuring Apache
    2323==================
    2424
    25 To check against Django's authorization database from a Apache configuration
    26 file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
    27 with the standard ``Auth*`` and ``Require`` directives:
     25To check against Django's authorization database from an Apache configuration
     26file, you can either use mod_python's ``PythonAccessHandler`` directive or
     27the ``PythonAuthenHandler`` directive along with the standard ``Auth*`` and
     28``Require`` directives.
    2829
     30The ``PythonAccessHandler`` directive validates using the built-in
     31``contrib.auth`` authentication, which uses the session cookie and redirects to
     32the ``settings.LOGIN_URL`` if authentication is required::
     33
    2934.. code-block:: apache
    3035
    3136    <Location /example/>
     37        SetEnv DJANGO_SETTINGS_MODULE mysite.settings
     38        PythonAccessHandler django.contrib.auth.handlers.modpython
     39    </Location>
     40
     41The ``PythonAuthenHandler`` directive just uses basic HTTP authentication::
     42
     43.. code-block:: apache
     44
     45    <Location /example/>
    3246        AuthType Basic
    3347        AuthName "example.com"
    3448        Require valid-user
     
    110124
    111125                                      By default no specific permission will be
    112126                                      required.
     127
     128    ``DjangoRaiseForbidden``          If the user authenticates but does not
     129                                      have the valid credentials, raise HTTP
     130                                      Error 403 (forbidden) rather providing a
     131                                      login prompt again.
     132
     133                                      Defaults to ``off``.
    113134    ================================  =========================================
    114135
     136You may also want to make Apache pass the correct headers to stop proxy servers
     137(and local browser caches) from caching your protected resources. Assuming that
     138the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache
     139server, you can add the following directives to your ``Location`` block::
     140
     141    # Stop resources from being cached
     142    ExpiresActive On
     143    ExpiresDefault A0
     144    Header append Cache-Control: "no-cache, no-store, must-revalidate, private"
     145
    115146Note that sometimes ``SetEnv`` doesn't play well in this mod_python
    116147configuration, for reasons unknown. If you're having problems getting
    117148mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using
     
    120151
    121152    SetEnv DJANGO_SETTINGS_MODULE mysite.settings
    122153    PythonOption DJANGO_SETTINGS_MODULE mysite.settings
     154
     155.. _authentication system: ../authentication/
     156.. _Subversion: http://subversion.tigris.org/
     157.. _mod_dav: http://httpd.apache.org/docs/2.0/mod/mod_dav.html
     158.. _custom permissions: ../authentication/#custom-permissions
     159
Back to Top