# -*- Mode: Python; tab-width: 4 -*-
#
# Author: Sam Rushing <rushing@nightmare.com>
# Copyright 1996-2000 by Sam Rushing
# All Rights Reserved.
#
RCS_ID = '$Id: auth_handler.py,v 1.3 2001/05/01 11:44:48 andreas Exp $'
# support for 'basic' authenticaion.
import base64
import md5
import re
import string
import time
import counter
import default_handler
get_header = default_handler.get_header
import http_server
import producers
# This is a 'handler' that wraps an authorization method
# around access to the resources normally served up by
# another handler.
# does anyone support digest authentication? (rfc2069)
class auth_handler:
def __init__ (self, dict, handler, realm='default'):
self.authorizer = dictionary_authorizer (dict)
self.handler = handler
self.realm = realm
self.pass_count = counter.counter()
self.fail_count = counter.counter()
def match (self, request):
# by default, use the given handler's matcher
return self.handler.match (request)
def handle_request (self, request):
# authorize a request before handling it...
scheme = get_header (AUTHORIZATION, request.header)
if scheme:
scheme = string.lower (scheme)
if scheme == 'basic':
cookie = AUTHORIZATION.group(2)
try:
decoded = base64.decodestring (cookie)
except:
print 'malformed authorization info <%s>' % cookie
request.error (400)
return
auth_info = string.split (decoded, ':')
if self.authorizer.authorize (auth_info):
self.pass_count.increment()
request.auth_info = auth_info
self.handler.handle_request (request)
else:
self.handle_unauthorized (request)
#elif scheme == 'digest':
# print 'digest: ',AUTHORIZATION.group(2)
else:
print 'unknown/unsupported auth method: %s' % scheme
self.handle_unauthorized()
else:
# list both? prefer one or the other?
# you could also use a 'nonce' here. [see below]
#auth = 'Basic realm="%s" Digest realm="%s"' % (self.realm, self.realm)
#nonce = self.make_nonce (request)
#auth = 'Digest realm="%s" nonce="%s"' % (self.realm, nonce)
#request['WWW-Authenticate'] = auth
#print 'sending header: %s' % request['WWW-Authenticate']
self.handle_unauthorized (request)
def handle_unauthorized (self, request):
# We are now going to receive data that we want to ignore.
# to ignore the file data we're not interested in.
self.fail_count.increment()
request.channel.set_terminator (None)
request['Connection'] = 'close'
request['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
request.error (401)
def make_nonce (self, request):
"A digest-authentication <nonce>, constructed as suggested in RFC 2069"
ip = request.channel.server.ip
now = str (long (time.time()))[:-1]
private_key = str (id (self))
nonce = string.join ([ip, now, private_key], ':')
return self.apply_hash (nonce)
def apply_hash (self, s):
"Apply MD5 to a string <s>, then wrap it in base64 encoding."
m = md5.new()
m.update (s)
d = m.digest()
# base64.encodestring tacks on an extra linefeed.
return base64.encodestring (d)[:-1]
def status (self):
# Thanks to mwm@contessa.phone.net (Mike Meyer)
r = [
producers.simple_producer (
'<li>Authorization Extension : '
'<b>Unauthorized requests:</b> %s<ul>' % self.fail_count
)
]
if hasattr (self.handler, 'status'):
r.append (self.handler.status())
r.append (
producers.simple_producer ('</ul>')
)
return producers.composite_producer (
http_server.fifo (r)
)
class dictionary_authorizer:
def __init__ (self, dict):
self.dict = dict
def authorize (self, auth_info):
[username, password] = auth_info
if (self.dict.has_key (username)) and (self.dict[username] == password):
return 1
else:
return 0
AUTHORIZATION = re.compile (
# scheme challenge
'Authorization: ([^ ]+) (.*)',
re.IGNORECASE
)
|