[Twisted-Python] htpasswd / HTTP basic auth example
Andrew Bennetts
andrew-twisted at puzzling.org
Sun Jul 20 19:27:16 MDT 2003
On Sun, Jul 20, 2003 at 08:39:27PM -0000, Moshe Zadka wrote:
> As many of you know, using guard (the webby interface to authentication)
> has been a thorny issue with Twisted. Christopher Armstrong and I wrote
> a module which hides many of the details of using guard, and supplies
> a much more usable interface.
This reminds me -- I wrote a simple resource wrapper recently that provides
HTTP basic auth, reading from a htpasswd(1) file [currently it only supports
crypt'ed passwords, not MD5]. This doesn't use guard (or even newcred) at
all, although it probably should.
I keep meaning to post it to the list; so here it is!
---- htpasswdauth.py ----
from crypt import crypt
from twisted.web import static
from twisted.web.resource import Resource
from twisted.protocols import http
__all__ = ['HtPasswdWrapper']
class UnauthorizedResource(Resource):
isLeaf = 1
def __init__(self, realm, errorPage):
Resource.__init__(self)
self.realm = realm
self.errorPage = errorPage
def render(self, request):
request.setResponseCode(http.UNAUTHORIZED)
# FIXME: Does realm need to be quoted?
request.setHeader('WWW-authenticate', 'basic realm="%s"' % self.realm)
return self.errorPage.render(request)
class HtPasswdWrapper(Resource):
"""Apache-style htpasswd protection for a resource.
Requires a client to authenticate (using HTTP basic auth) to access a
resource. If they fail to authenticate, or their username and password
aren't accepted, they receive an error page.
The username and password are checked against a htpasswd(1) file using
crypt. The file is re-read for every request.
TODO:
- Integrate this into twisted.web.woven.guard / newcred?
- Support MD5 password hashes in the htpasswd file, as well as crypt.
@cvar unauthorizedPage: L{Resource} that will be used to render the error
page given when a user is unauthorized.
"""
unauthorizedPage = static.Data(
'<html><body>Access Denied.</body></html>', 'text/html'
)
def __init__(self, resource, htpasswdFilename, realm):
"""Constructor.
@param resource: resource to protect with authentication.
@param htpasswdFilename: filename of an htpasswd file to authenticate
with. Currently only crypt(3)-format passwords are supported.
@param realm: HTTP auth realm.
"""
Resource.__init__(self)
self.resource = resource
self.filename = htpasswdFilename
self.realm = realm
def getChildWithDefault(self, path, request):
if self.authenticateUser(request):
return self.resource.getChildWithDefault(path, request)
else:
return self.unauthorized()
def render(self, request):
if self.authenticateUser(request):
return self.resource.render(request)
else:
return self.unauthorized().render(request)
def authenticateUser(self, request):
username, password = request.getUser(), request.getPassword()
lines = [l.rstrip().split(':', 1) for l in file(self.filename).readlines()]
lines = [l for l in lines if l[0] == username]
if not lines:
return 0
hashedPassword = lines[0][1]
return hashedPassword == crypt(password, hashedPassword[:2])
def unauthorized(self):
return UnauthorizedResource(self.realm, self.unauthorizedPage)
if __name__ == '__main__':
# Quick & dirty testing...
# create the intarweb
from twisted.web.server import Site
root = Resource()
sit = Site(HtPasswdWrapper(root, '/tmp/htpasswdtest', 'test site'))
#sit = Site(root)
root.putChild('', static.Data('If you can see this, you are authorized! Congrats!', 'text/plain'))
root.putChild('blah', static.Data('Bring me a child!!', 'text/plain'))
# and finally talk to the internat
from twisted.internet import reactor
reactor.listenTCP(18080, sit)
reactor.run()
---- end ----
-Andrew.
More information about the Twisted-Python
mailing list