As most are aware, Apache is a very modular, highly developer-friendly web server. It still serves more web content worldwide than any other web server. The problem for some is that it is written in C and for you to write custom functionality for Apache, this usually means writing an Apache module in C. What you probably didn't know is that there is an easier way and it is due to mod_python.
mod_python is "an Apache module that embeds the Python interpreter within the server". That is kind of vague, and it's even suggested on the mod_python homepage to read the "Introducing mod_python" article from O'Reilly. To save you the trouble, the important parts in the context we're discussing, mod_python provides the following
- A handler to the Apache request processing phaes, like authentication (authn) and authorization (authz)
- An interface to a subset of the Apache API, which means you can call internal Apache functions from Python
Having access to the internal Apache API, or at least some of it, and being able to handle the Apache phases are the parts we're interested in, since a few of those phases are devoted to authn/authz. Before going into any examples or details on how to use these features for creating your own authn/authz module using mod_python, let's go through a very simple series of steps for hooking mod_python into Apache for your application. (We will not be talking about how to install mod_python since there is a plethora of information about this online.) For this example, we'll get talking about how you could use mod_python to create your own custom authn/authz for Subversion.
Hook mod_python into Apache
As usual, there are many ways to do this but since our example is for authentication/authorizing Subversion access, we'll be putting our mod_python stuff within a standard "Location" block. Here is a simple Location block that we will be starting with:
# Work around authz and SVNListParentPath issue
RedirectMatch ^(/repos)$ $1/
# Enable Subversion logging
CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION
<Location /repos/>
# Enable Subversion
DAV svn
# Directory containing all repository for this path
SVNParentPath /opt/repos/svn
# Enable repository listing when browing the Location root
SVNListParentPath On
# Enable WebDAV automatic versioning
SVNAutoversioning On
# Repository Display Name when browsing with the built-in repository browser
SVNReposName "Your Subversion Repository""
</Location>The Location block above is one of the simplest ways to expose a Subversion repository. As you can see there is no authn/authz stuff in here meaning right now, if you exposed your repositories using this Location block, you'd have free roam to do whatever you wanted to the repositories and their contents. Now...let's update our Location block to have the necessary bits to require Apache authentication and to make our Python script the handler of the authn/authz Apache phases.
# Work around authz and SVNListParentPath issue
RedirectMatch ^(/repos)$ $1/
# Enable Subversion logging
CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION
<Location /repos/>
# Enable Subversion
DAV svn
# Directory containing all repository for this path
SVNParentPath /opt/repos/svn
# Enable repository listing when browing the Location root
SVNListParentPath On
# Enable WebDAV automatic versioning
SVNAutoversioning On
# Repository Display Name when browsing with the built-in repository browser
SVNReposName "Your Subversion Repository""
# Do basic password authentication in the clear
AuthType Basic
# The name of the protected area or "realm"
AuthName "Your Subversion Repository"
# Require authentication
Require valid-user
# Make Apache aware that we want to use mod_python
AddHandler mod_python .py
# Specify the callable object to handle the authn phase
PythonAuthenHandler svnauth
# Specify the callable object to handle the authz phase
PythonAuthzHandler svnauth
# Optionally extend the Python path to locate your callable object
PythonPath "sys.path+['/opt/repos/scripts']"
</Location>To better understand the options for the mod_python directives in the Location block, here are a few direct links within the mod_python documentation:
Alright...now that we have Apache ready to use mod_python, we need to create the script used for auth/authz. Since the PythonAuthenHandler and PythonAuthzHandler just specify a module name, we need to have a file named svnauth.py somewhere on the Python path. We can put it into /opt/repos/scripts since we used the PythonPath directive to updated the Python path to include that directory. Now on to the file's contents.
Since we just specified a module name in the mod_python handler directives, we need to follow a specific naming convention for our handler function names. The convention is that you basically strip off the "Python" part of the handler directive's name, and create a function in your module with that name, all lower case of course, that accepts one argument, the Apache request object. Here is a very simple example of our svnauth.py:
#!/usr/bin/env python # -*-python-*- # # Simple example of using mod_python to handle # Apache's authn and authz phases. from mod_python import apache def authenhandler(req): """This function gets called by mod_python to handle Apache's authentication phase""" return apache.OK def authzhandler(req): """This function gets called by mod_python to handle Apache's authorization phase""" return apache.OK
Pretty simple huh? As the functions stand now, they just return "apache.OK" which instructs Apache that the requested user is authentication and authorized to access the repositories. So...now that you know how to hook up mod_python to handle the Apache phases, like the authentication and authorization phases, where do you go from here? Well, that is up to you. This "article" is only to help get you started. But with me being a nice guy, here are some examples of how you might use this knowledge not only for Subversion but for any Apache-based application where you need to use your own authentication/authorization measures:
- Making web service calls to authenticate/authorize a user
- Implementing single sign on for your application
- Whenever your company stores authn/authz information in a way that is not accessible by conventional Apache modules, like storing your Subversion authz information in a database instead of the typical authz file
The possibilities for using mod_python for authn/authz are endless. Honestly, the possibilities for using mod_python for any of the Apache phases are endless. Sure beats writing C-based Apache modules when it comes to simplicity.
As for what we've learned, I hope that I've taught you enough about mod_python and how it can be used with Apache for creating your own pseudo Apache modules. The example I gave is a real-world example where you can use mod_python to implement your own authn/authz for Subversion but that is just an example. Anywhere Apache is used in your infrastructure, mod_python can be used to make hooking into Apache simple and even provide you the ability to do things that the built-in Apache modules don't provide.



Comments
Is it possible to use
Is it possible to use sessions (or some other method) to pass on the username and authentication data to the main mod_python program?
I would like to allow users to use basic auth (over ssl) as another optional way of logging in to my standard mod_python app.
Well, since each mod_python
Well, since each mod_python handler has access to the username and password given by the user, you can use it however you please.
Jeremy, Thank you very much
Jeremy,
Thank you very much for your amazing article. It was very helpful for me!
The only thing I had to do is to add
directive for Apache 2.2 to make it work.
Alex
I guess I'm a bit late
I guess I'm a bit late commenting on this article, I just discovered it and the PythonAuthzHandler directive with it.
I'm very interested in off-loading Subversion authorization from mod_auth_svn to my own custom python code (I won't go into the details here as they aren't particularly relevant). Using the information above, I've been able to get things working as I'd like however, I'm still having one problem that I can't figure out.
My problem involves anonymous access. The only way I can ensure that my custom authz handler is called is by using the Require directive. However the Require directive causes the user to be prompted for credentials before my custom authz handler code is entered. Instead I would like my authz handler to be called first, in order to attempt anonymous access and then request credentials if anonymous access fails. A method for doing this (using mod_auth_svn) is described in the Subversion book. I've tried variations of what is described in the book, but including the "Satisfy Any" directive in my apache config causes my custom authz handler to be bypassed entirely.
Is there any way to force my custom authz handler to be called first, without using the Require directive? I've tried including:
but these don't seem to have any effect.
Does anyone have thoughts on how to achieve what I'm describing? Any help would be greatly appreciated.
Well, having the Require in
Well, having the Require in there is what makes Apache do work. The best thing to do would be to have a Location for anonymous and a Location for those repositories requiring authentication. I'm currently not aware of a way to work around this but I'll play around with it tomorrow and let you know if something turns up.