Service BASIC HTTP Authentication with Python

While building the core parts of OpenGroupware COILS I noticed there don't appear to be any example of providing HTTP's BASIC authentication scheme anywhere on the interweb. Or any other authentication schemes for that matter. To remedy that here is a rough outline, including the example code, of how it is implemented in COILS. First spin up an HTTP server:

import BaseHTTPServer

class HTTPServer(BaseHTTPServer.HTTPServer):

from coils.net.handler import HTTPRequestHandler
from coils.net.server import HTTPServer
HTTP_HOST = 'localhost'
HTTP_PORT = 8080
httpd = HTTPServer((HTTP_HOST, HTTP_PORT), HTTPRequestHandler)

Your app will almost certainly provide a custom HTTPRequestHandler to delegate the requests to whatever logic your application provides. In the BaseHTTPRequestHandler a GET request is handed to do_GET, a POST request to do_POST, etc... In our case the object that handles the request will decide what to do based on the request type so we channel all requests to our generic process_request method. process_request looks up the object targeted by the request via marshall_handler() [not shown] and then calls that objects do_request method. The important part for authentication is to catch the exception raised by the object handling the request if that object thinks the request is not authenticated and requires authentication. In this case that is the COILS' AuthenticationException; your application has to provide something equivalent. In order to make the client try again with authentication you need to send a "WWW-Authenticate" header telling the client to use basic authentication and what realm to use. See RFC2617 for details on Basic authentication in general

class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

def process_request(self):
"""Respond to a request"""
""" find the object mapped to the specified request
The marshall_handler() is a COILS specific things so it isn't
shown in this example. """
handler = self.marshall_handler()
except AuthenticationException, err:
""" An AuthenticationException has an error code of 401
We need to add an authentication header so the client will know
to respond the the failure with the appropriate credentials
TODO: Provide a digest realm once digest authentication is supported
self.send_header('WWW-Authenticate', 'Basic realm="OpenGroupware COILS"')
self.wfile.write('Authentication failure')
except CoilsException, err:
# An Coils Exception has an error code of 500
self.send_response(err.error_code(), err.error_text())
except Exception, err:
# Yikes, something generic web very wrong
self.send_response(500, err)

def do_GET(self):
"""Respond to a GET request."""

def do_POST(self):
"""Respond to a POST request"""

In order to actually process the authentication request COILS uses an Authenticator object. Our DBAuthenticator object provides basic authentication against accounts with passwords stored directly in the database [verses accounts from LDAP or trusting an external authorization mechanism such as Kerberos]. Explanation for the steps to process a Basic authentication operation have been added to the code example:

from base64 import b64decode
from crypt import crypt
class DBAuthenticator(Authenticator):

def _authenticate(self, context, request):
Authenticator._authenticate(self, context, request)
authorization = request.headers.get('authorization')
if (authorization == None):
raise AuthenticationException('Authentication Required')
(kind, data) = authorization.split(' ')
if (kind == 'Basic'):
# Authentication method is "Basic"
(username, _, password) = b64decode(data).partition(':')
""" This method provided by the parent class goes to the ORM and
retrieves the account object for the specified username. It
will throw an authentication exception if no such username is
found (user entered it wrong?) or a generic Coils Exception if
multiple objects match the username (that doesn't make sense) -
either will stop the authentication process, but the authentication
exception should reprompt the client to try again. """
account = Authenticator._getLogin(self, username)
secret = account.password
if (secret == crypt(password, secret[:2])):
# Password matches, user is authenticated
self.loginId = account.objectId
self.login = account.login
# Password does not match, authentication failes
raise AuthenticationException('Incorrect username or password')
# Authorization header indicated an authentication type other than BASIC
CoilsException('Unsupported HTTP Authenticated Mech')

See, that easy easy.


GRLUG OpenGroupware Presentation

Last night I presented on OpenGroupware to the Grand Rapids LINUX User's Group.

Presentation File

Multiple Interfaces On Windows

A great post over on Ivan Zlatev's BLOG on how to get a working configuration of multiple network interfaces on a Microsoft Windows workstation. There probably isn't anyone who has been forced to use that runt of an OS and not been frustrated by how it handles network connections.

Database Changes to OpenGroupware v5.5

For those building from trunk, or currently pulling packages from OBS, there is a new table required as of r2256.

INSERT INTO ctags (entity) VALUES ('Person');
INSERT INTO ctags (entity) VALUES ('Enterprise');
INSERT INTO ctags (entity) VALUES ('Date');
INSERT INTO ctags (entity) VALUES ('Job');
INSERT INTO ctags (entity) VALUES ('Team');

This is part of adding ctag support to OGo (specifically ZideStore). ctags allow a client to *very* quickly detect if the contents of a collection have changed; such as the /public/Contacts folder. Thus avoiding doing a PROPFIND on the entire folder to determine if a re-sync is needing. ctag support is not complete or working yet, but without this table a server post-r2256 will fails some operations with a database error.

The subversion repository contains an update script for migrating a v5.4 database to v5.5.


Announcing OpenGroupware COILS

I've create a new project on the shiny new SourceForge: OpenGroupware COILS. This is "a re-implementation of the groupware functionality provided by OpenGroupware.org. COILS is parallel installable and is entirely compatible with the Objective-C version 5.5 of OpenGroupware.org". COILS is developed in Python for reasons explained on the Wiki. If you'd like to participate in the development of COILS please join the COILS mail list. I also hang out on the OGo IRC channel on FreeNode. And no, this does not mean I am going to stop working on the Objective-C OGo. But I believe there is a need for a refreshing of the OGo platform (BTW, there is also OGo/J) and there are some ideas I want to explore that really can't be done [easily] in the existing OGo/ObjC codebase.