""" mod_python authentication Usage: In httpd.conf, you should add these lines: LoadModule python_module libexec/apache2/mod_python.so # Be sure this script is accessible through one of these paths: # (/usr/local/lib/python2.3 is also required) PythonPath "['/usr/local/lib/python2.3', '/usr/local/lib/python2.3/plat-freebsd4', '/usr/local/lib/python2.3/lib-dynload', '/usr/local/lib/python2.3/site-packages', '/usr/local/lib/python2.3/site-packages/Numeric', '/usr/local/lib/python2.3/site-packages/PIL']" # If you want to test stuff: #PythonDebug on PythonHeaderParserHandler path.to.authen PythonOption login_screen /cgi-bin/login.cgi PythonOption login_required yes You can use 'PythonOption login_required yes' in a or . The login_screen can be set locally or globally. The screen can be a CGI script, Zope page, or whatever. It needs only set the appropriate cookie on successful login. This also provides the REMOTE_USER variable. Unlike HTTP Basic authentication, you do not have to reject an anonymous user before the user will appear logged in (and two requests will not have to be made). This way unprotected pages can see what the current user is (or '' if no user). This cannot catch 401 errors, so unauthorized errors will cause problems with this script. Unfortunately while you can set ErrorDocument 401 /cgi-bin/login.cgi, the user will only see that screen after they cancel the Basic login. They will not be able to login through the Basic login popup, causing much confusion. You can also import this module for use in the login screen. The two applicable functions in that case are authorize_user_cookie(username) and unauthorize_user_cookie(). """ try: from mod_python import apache from mod_python import Cookie except ImportError: # this happens if you use this outside of mod_python, which # is okay. pass import md5 import time # @@: This shouldn't be hardcoded; but including it only in the Apache # config makes it hard for programs outside of Apache to access the # secret. It probably should be kept in a file. Security of such a # file is important, but it's not very important to protect such a # file from a local exploit. SECRET = 'myspecialsecret' def headerparserhandler(req): """ The actual authenticating check. This checks if there is a proper cookie in the request, and sets the request based on that. It also clears out any attempt to do HTTP Basic authentication. The cookie should look like sig:user_enc, where user_enc is the username encoded in base64. The signature is the secret (SECRET at the top of this file) concatenated with the unencoded username. """ # First we clear out any stale login information; we are in # this process disallowing HTTP Basic login, because we are # actually avoiding doing any password checking here. req.user = '' cookies = Cookie.get_cookies(req) if cookies.has_key('authinfo'): authinfo = cookies['authinfo'].value # We decode the cookie and confirm the signature, # lastly setting the username. try: if ':' not in authinfo: raise ValueError, "Badly formatted cookie" sig, username = authinfo.split(':', 1) username = username.decode('base64') if md5.new(SECRET + username).hexdigest() != sig: raise ValueError, "Bad cookie signature" req.user = username except ValueError, msg: req.user = '' # We get rid of any spurious Authorization headers, and # put in a fake authorization header if there is a user # logged in. if req.user: req.headers_in['Authorization'] = ( 'Basic ' + (req.user + ':').encode('base64')) else: if req.headers_in.has_key('Authorization'): del req.headers_in['Authorization'] return check_authorization(req) def check_authorization(req): """ Checks if the user is authorized. Currently the only restriction is PythonOption login_required yes; there's no other ways to restrict users. """ ops = req.get_options() if (ops.get('login_required') and ops['login_required'].lower().startswith('y')): if not req.user: redir = ops.get('login_screen') if redir: req.internal_redirect(redir) return apache.OK else: return apache.HTTP_UNAUTHORIZED return apache.OK def authorize_user_cookie(username): """ Use like: print 'Set-Cookie: ' + authorize_user_cookie('bob') """ sig = md5.new(SECRET + username).hexdigest() return 'authinfo=%s:%s; Path=/;' % (sig, username.encode('base64').strip()) def unauthorize_user_cookie(): """ Used for logout, like: print 'Set-Cookie: ' + unauthorize_user_cookie() """ return ('authinfo=; Path=/; expires=%s' % time.strftime(time.gmtime(), '%a, %d-%b-%Y %H:%M:%S GMT'))