[mod_python] Apache 2.2 authen/authz and "require" semantics

Graham Dumpleton graham.dumpleton at gmail.com
Mon Sep 24 07:21:46 EDT 2007


On 24/09/2007, Arnar Birgisson <arnarbi at gmail.com> wrote:
> On 9/24/07, Graham Dumpleton <graham.dumpleton at gmail.com> wrote:
> > On 24/09/2007, Arnar Birgisson <arnarbi at gmail.com> wrote:
> > > Can I return apache.HTTP_UNAUTHORIZED from a fixup-handler to make the
> > > browser request username/passwd?
> >
> > Technically you can. The issue will be that if you have defined
> > AuthType etc then the earlier auth handler phase may result in it not
> > getting that far.
>
> Would I perform the authentication in the fixup handler as well?
> Basically just do it all there?
>
> 1. find project name
> 2. lookup project in db
> 3. if anon access allowed - apache.OK
> 4. call req.get_basic_auth_pw()
> 5. lookup user - apache.HTTP_UNAUTHORIZED if not found
> 6. check passwd - apache.HTTP_UNAUTHORIZED if no match
> 7. check for user access - apache.OK if allowed
> 8. apache.HTTP_UNAUTHORIZED otherwise

But then you may as well do it all in the authentication handler and
just use 'Require valid-user' as the authorisation phase, although you
are strictly mixing up the intent of what the phases are all about.

Thus we get back to perhaps just doing it properly in the first place
but where mod_python doesn't exactly make it easy. This is partly
because of the incomplete exposure of the Apache APIs through
mod_python, partly because mod_python Basic authentication handling is
historically incorrect and has encouraged sloppy practices and partly
because of incomplete documentation for mod_python on how to do it
properly. :-(

At this point I scream and wish I had finished the auth/authz support
in mod_wsgi which will just make this all so much easier. :-)

For example, in mod_wsgi (unreleased 2.0), your Apache configuration
would be something (names of things still changing) like:

<AuthnProviderAlias wsgi django>
WSGIAuthenticationGroup django
AuthWSGIUserScript /usr/local/django/mysite/apache/auth.wsgi
</AuthnProviderAlias>

WSGIScriptAlias / /usr/local/django/mysite/apache/django.wsgi

<Directory /usr/local/django/mysite/apache>
Order deny,allow
Allow from all

WSGIApplicationGroup django

AuthType Basic
AuthName "Django Site"
AuthBasicProvider django
Require valid-user
</Directory>

Here the authentication is being applied to the Django site itself,
but could also be applied to Trac /login URL or Subversion access.

The auth script would then just be:

import os, sys
sys.path.append('/usr/local/django')
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

import apache.mod_auth
from django.contrib.auth.models import User
from django import db

def check_password(environ, user, password):
    db.reset_queries()

    kwargs = {'username': req.user, 'is_active': True}

    try:
        try:
            user = User.objects.get(**kwargs)
        except User.DoesNotExist:
            return apache.mod_auth.AUTH_USER_NOT_FOUND

        if user.check_password(password):
            return apache.mod_auth.AUTH_GRANTED
        else:
            return apache.mod_auth.AUTH_DENIED
    finally:
        db.connection.close()

None of the worrying about HTTP headers, password decoding etc as
Apache does that all for you. You only have to worry about the
password check.

The bit I am still working on is the authorisation, ie., group access
bits. Using your example, this would be something like:

Require wsgi-group svn_read
<LimitExcept GET PROPFIND OPTIONS REPORT>
   Require wsgi-group svn_write
</LimitExcept>

Am looking at couple of different options at present as to how this
would be checked on Python side. One is to have:

def groups_for_user(environ, user):
    db.reset_queries()

    kwargs = {'username': req.user, 'is_active': True}

    try:
        try:
            user = User.objects.get(**kwargs)
        except User.DoesNotExist:
            return None

         return user.groups

    finally:
        db.connection.close()

The mod_wsgi module would then process the Require directives and
deliver the necessary response.

In the greater scheme of things, all much simpler.

In the mean time, I'll try and find some time to explain how to do it
for mod_python properly. Cant promise anything though as starting to
get backlogged as it is.

Graham


More information about the Mod_python mailing list