Revision bf9bd8dd lib/http/auth.py
b/lib/http/auth.py | ||
---|---|---|
32 | 32 |
|
33 | 33 |
from cStringIO import StringIO |
34 | 34 |
|
35 |
try: |
|
36 |
from hashlib import md5 |
|
37 |
except ImportError: |
|
38 |
from md5 import new as md5 |
|
39 |
|
|
35 | 40 |
|
36 | 41 |
# Digest types from RFC2617 |
37 | 42 |
HTTP_BASIC_AUTH = "Basic" |
... | ... | |
75 | 80 |
# Default authentication realm |
76 | 81 |
AUTH_REALM = None |
77 | 82 |
|
83 |
# Schemes for passwords |
|
84 |
_CLEARTEXT_SCHEME = "{CLEARTEXT}" |
|
85 |
_HA1_SCHEME = "{HA1}" |
|
86 |
|
|
78 | 87 |
def GetAuthRealm(self, req): |
79 | 88 |
"""Returns the authentication realm for a request. |
80 | 89 |
|
... | ... | |
198 | 207 |
""" |
199 | 208 |
raise NotImplementedError() |
200 | 209 |
|
210 |
def VerifyBasicAuthPassword(self, req, username, password, expected): |
|
211 |
"""Checks the password for basic authentication. |
|
212 |
|
|
213 |
As long as they don't start with an opening brace ("{"), old passwords are |
|
214 |
supported. A new scheme uses H(A1) from RFC2617, where H is MD5 and A1 |
|
215 |
consists of the username, the authentication realm and the actual password. |
|
216 |
|
|
217 |
@type req: L{http.server._HttpServerRequest} |
|
218 |
@param req: HTTP request context |
|
219 |
@type username: string |
|
220 |
@param username: Username from HTTP headers |
|
221 |
@type password: string |
|
222 |
@param password: Password from HTTP headers |
|
223 |
@type expected: string |
|
224 |
@param expected: Expected password with optional scheme prefix (e.g. from |
|
225 |
users file) |
|
226 |
|
|
227 |
""" |
|
228 |
# Backwards compatibility for old-style passwords without a scheme |
|
229 |
if not expected.startswith("{"): |
|
230 |
expected = self._CLEARTEXT_SCHEME + expected |
|
231 |
|
|
232 |
# Check again, just to be sure |
|
233 |
if not expected.startswith("{"): |
|
234 |
raise AssertionError("Invalid scheme") |
|
235 |
|
|
236 |
scheme_end_idx = expected.find("}", 1) |
|
237 |
|
|
238 |
# Ensure scheme has a length of at least one character |
|
239 |
if scheme_end_idx <= 1: |
|
240 |
logging.warning("Invalid scheme in password for user '%s'", username) |
|
241 |
return False |
|
242 |
|
|
243 |
scheme = expected[:scheme_end_idx + 1].upper() |
|
244 |
expected_password = expected[scheme_end_idx + 1:] |
|
245 |
|
|
246 |
# Good old plain text password |
|
247 |
if scheme == self._CLEARTEXT_SCHEME: |
|
248 |
return password == expected_password |
|
249 |
|
|
250 |
# H(A1) as described in RFC2617 |
|
251 |
if scheme == self._HA1_SCHEME: |
|
252 |
realm = self.GetAuthRealm(req) |
|
253 |
if not realm: |
|
254 |
# There can not be a valid password for this case |
|
255 |
return False |
|
256 |
|
|
257 |
expha1 = md5() |
|
258 |
expha1.update("%s:%s:%s" % (username, realm, password)) |
|
259 |
|
|
260 |
return (expected_password.lower() == expha1.hexdigest().lower()) |
|
261 |
|
|
262 |
logging.warning("Unknown scheme '%s' in password for user '%s'", |
|
263 |
scheme, username) |
|
264 |
|
|
265 |
return False |
|
266 |
|
|
201 | 267 |
|
202 | 268 |
class PasswordFileUser(object): |
203 | 269 |
"""Data structure for users from password file. |
Also available in: Unified diff