auth_handler.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # -*- Mode: Python -*-
  2. #
  3. # Author: Sam Rushing <rushing@nightmare.com>
  4. # Copyright 1996-2000 by Sam Rushing
  5. # All Rights Reserved.
  6. #
  7. RCS_ID = '$Id: auth_handler.py,v 1.6 2002/11/25 19:40:23 akuchling Exp $'
  8. # support for 'basic' authentication.
  9. import re
  10. import time
  11. from supervisor.compat import encodestring, decodestring
  12. from supervisor.compat import md5
  13. from supervisor.compat import as_string, as_bytes
  14. from supervisor.compat import print_function
  15. import supervisor.medusa.counter as counter
  16. import supervisor.medusa.default_handler as default_handler
  17. get_header = default_handler.get_header
  18. import supervisor.medusa.producers as producers
  19. # This is a 'handler' that wraps an authorization method
  20. # around access to the resources normally served up by
  21. # another handler.
  22. # does anyone support digest authentication? (rfc2069)
  23. class auth_handler:
  24. def __init__ (self, dict, handler, realm='default'):
  25. self.authorizer = dictionary_authorizer (dict)
  26. self.handler = handler
  27. self.realm = realm
  28. self.pass_count = counter.counter()
  29. self.fail_count = counter.counter()
  30. def match (self, request):
  31. # by default, use the given handler's matcher
  32. return self.handler.match (request)
  33. def handle_request (self, request):
  34. # authorize a request before handling it...
  35. scheme = get_header (AUTHORIZATION, request.header)
  36. if scheme:
  37. scheme = scheme.lower()
  38. if scheme == 'basic':
  39. cookie = get_header (AUTHORIZATION, request.header, 2)
  40. try:
  41. decoded = as_string(decodestring(as_bytes(cookie)))
  42. except:
  43. print_function('malformed authorization info <%s>' % cookie)
  44. request.error (400)
  45. return
  46. auth_info = decoded.split(':', 1)
  47. if self.authorizer.authorize (auth_info):
  48. self.pass_count.increment()
  49. request.auth_info = auth_info
  50. self.handler.handle_request (request)
  51. else:
  52. self.handle_unauthorized (request)
  53. #elif scheme == 'digest':
  54. # print 'digest: ',AUTHORIZATION.group(2)
  55. else:
  56. print('unknown/unsupported auth method: %s' % scheme)
  57. self.handle_unauthorized(request)
  58. else:
  59. # list both? prefer one or the other?
  60. # you could also use a 'nonce' here. [see below]
  61. #auth = 'Basic realm="%s" Digest realm="%s"' % (self.realm, self.realm)
  62. #nonce = self.make_nonce (request)
  63. #auth = 'Digest realm="%s" nonce="%s"' % (self.realm, nonce)
  64. #request['WWW-Authenticate'] = auth
  65. #print 'sending header: %s' % request['WWW-Authenticate']
  66. self.handle_unauthorized (request)
  67. def handle_unauthorized (self, request):
  68. # We are now going to receive data that we want to ignore.
  69. # to ignore the file data we're not interested in.
  70. self.fail_count.increment()
  71. request.channel.set_terminator (None)
  72. request['Connection'] = 'close'
  73. request['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
  74. request.error (401)
  75. def make_nonce (self, request):
  76. """A digest-authentication <nonce>, constructed as suggested in RFC 2069"""
  77. ip = request.channel.server.ip
  78. now = str(long(time.time()))
  79. if now[-1:] == 'L':
  80. now = now[:-1]
  81. private_key = str (id (self))
  82. nonce = ':'.join([ip, now, private_key])
  83. return self.apply_hash (nonce)
  84. def apply_hash (self, s):
  85. """Apply MD5 to a string <s>, then wrap it in base64 encoding."""
  86. m = md5()
  87. m.update (s)
  88. d = m.digest()
  89. # base64.encodestring tacks on an extra linefeed.
  90. return encodestring (d)[:-1]
  91. def status (self):
  92. # Thanks to mwm@contessa.phone.net (Mike Meyer)
  93. r = [
  94. producers.simple_producer (
  95. '<li>Authorization Extension : '
  96. '<b>Unauthorized requests:</b> %s<ul>' % self.fail_count
  97. )
  98. ]
  99. if hasattr (self.handler, 'status'):
  100. r.append (self.handler.status())
  101. r.append (
  102. producers.simple_producer ('</ul>')
  103. )
  104. return producers.composite_producer(r)
  105. class dictionary_authorizer:
  106. def __init__ (self, dict):
  107. self.dict = dict
  108. def authorize (self, auth_info):
  109. [username, password] = auth_info
  110. if username in self.dict and self.dict[username] == password:
  111. return 1
  112. else:
  113. return 0
  114. AUTHORIZATION = re.compile (
  115. # scheme challenge
  116. 'Authorization: ([^ ]+) (.*)',
  117. re.IGNORECASE
  118. )