diff options
Diffstat (limited to 'wsgitools/digest.py')
-rw-r--r-- | wsgitools/digest.py | 143 |
1 files changed, 48 insertions, 95 deletions
diff --git a/wsgitools/digest.py b/wsgitools/digest.py index 83fbd65..ded5e8d 100644 --- a/wsgitools/digest.py +++ b/wsgitools/digest.py @@ -24,6 +24,9 @@ import base64 import time import os +from wsgitools.authentication import AuthenticationRequired, \ + ProtocolViolation, AuthenticationMiddleware + sysrand = random.SystemRandom() def gen_rand_str(bytes=33): @@ -105,15 +108,6 @@ def parse_digest_response(data, ret=None): value, data = data.split(',', 1) result[key] = value -class AuthenticationRequired(Exception): - """ - Internal Exception class that is thrown inside L{AuthDigestMiddleware}, but - not visible to other code. - """ - -class ProtocolViolation(AuthenticationRequired): - pass - def format_digest(mapping): """internal @@ -667,15 +661,16 @@ def check_uri(credentials, environ): raise AuthenticationRequired("url mismatch") __all__.append("AuthDigestMiddleware") -class AuthDigestMiddleware: +class AuthDigestMiddleware(AuthenticationMiddleware): """Middleware partly implementing RFC2617. (md5-sess was omited) Upon successful authentication the environ dict will be extended by a REMOTE_USER key before being passed to the wrapped application.""" + authorization_method = "digest" algorithms = {"md5": lambda data: md5(data).hexdigest()} def __init__(self, app, gentoken, maxage=300, maxuses=5, store=None): """ - @param app: is the wsgi application to be served with authentification. + @param app: is the wsgi application to be served with authentication. @type gentoken: str -> (str or None) @param gentoken: has to have the same functionality and interface as the L{AuthTokenGenerator} class. @@ -688,7 +683,7 @@ class AuthDigestMiddleware: @param store: a nonce storage implementation object. Usage of this parameter will override maxage and maxuses. """ - self.app = app + AuthenticationMiddleware.__init__(self, app) self.gentoken = gentoken if store is None: self.noncestore = MemoryNonceStore(maxage, maxuses) @@ -697,78 +692,56 @@ class AuthDigestMiddleware: assert hasattr(store, "checknonce") self.noncestore = store - def __call__(self, environ, start_response): - """wsgi interface""" + def authenticate(self, auth, environ): + try: + credentials = parse_digest_response(auth) + except ValueError: + raise ProtocolViolation("failed to parse digest response") + + ### Check algorithm field + credentials["algorithm"] = credentials.get("algorithm", + "md5").lower() + if not credentials["algorithm"] in self.algorithms: + raise ProtocolViolation("algorithm not implemented: %r" % + credentials["algorithm"]) + + check_uri(credentials, environ) try: + nonce = credentials["nonce"] + credresponse = credentials["response"] + except KeyError, err: + raise ProtocolViolation("%s missing in credentials" % + err.args[0]) + noncecount = 1 + if "qop" in credentials: + if credentials["qop"] != "auth": + raise ProtocolViolation("unimplemented qop: %r" % + credentials["qop"]) try: - auth = environ["HTTP_AUTHORIZATION"] + noncecount = int(credentials["nc"], 16) except KeyError: - raise AuthenticationRequired("no Authorization header found") - try: - method, rest = auth.split(' ', 1) + raise ProtocolViolation("nc missing in qop=auth") except ValueError: - method, rest = auth, "" + raise ProtocolViolation("non hexdigit found in nonce count") - if method.lower() != "digest": - raise AuthenticationRequired( - "authorization method not implemented: %r" % method) - try: - credentials = parse_digest_response(rest) - except ValueError: - raise ProtocolViolation("failed to parse digest response") + # raises AuthenticationRequired + response = self.auth_response(credentials, + environ["REQUEST_METHOD"]) - ### Check algorithm field - credentials["algorithm"] = credentials.get("algorithm", - "md5").lower() - if not credentials["algorithm"] in self.algorithms: - raise ProtocolViolation("algorithm not implemented: %r" % - credentials["algorithm"]) + if not self.noncestore.checknonce(nonce, noncecount): + raise StaleNonce() - check_uri(credentials, environ) + if response is None or response != credresponse: + raise AuthenticationRequired("wrong response") - try: - nonce = credentials["nonce"] - credresponse = credentials["response"] - except KeyError, err: - raise ProtocolViolation("%s missing in credentials" % - err.args[0]) - noncecount = 1 - if "qop" in credentials: - if credentials["qop"] != "auth": - raise ProtocolViolation("unimplemented qop: %r" % - credentials["qop"]) - try: - noncecount = int(credentials["nc"], 16) - except KeyError: - raise ProtocolViolation("nc missing in qop=auth") - except ValueError: - raise ProtocolViolation("non hexdigit found in nonce count") - - # raises AuthenticationRequired - response = self.auth_response(credentials, - environ["REQUEST_METHOD"]) - - if not self.noncestore.checknonce(nonce, noncecount): - raise StaleNonce() - - if response is None or response != credresponse: - raise AuthenticationRequired("wrong response") - - except AuthenticationRequired, exc: - return self.authorization_required(environ, start_response, exc) - else: - environ["REMOTE_USER"] = credentials["username"] - def modified_start_response(status, headers, exc_info=None): - digest = dict(nextnonce=self.noncestore.newnonce()) - if "qop" in credentials: - digest["qop"] = "auth" - digest["cnonce"] = credentials["cnonce"] # no KeyError - digest["rspauth"] = self.auth_response(credentials, "") - challenge = format_digest(digest) - headers.append(("Authentication-Info", challenge)) - return start_response(status, headers, exc_info) - return self.app(environ, modified_start_response) + digest = dict(nextnonce=self.noncestore.newnonce()) + if "qop" in credentials: + digest["qop"] = "auth" + digest["cnonce"] = credentials["cnonce"] # no KeyError + digest["rspauth"] = self.auth_response(credentials, "") + return dict(user=credentials["username"], + outheaders=[("Authentication-Info", format_digest(digest))]) def auth_response(self, credentials, reqmethod): """internal method generating authentication tokens @@ -806,13 +779,6 @@ class AuthDigestMiddleware: return self.algorithms[algo](":".join(dig)) def www_authenticate(self, exception): - """Generates a WWW-Authenticate header. - - @type exception: AuthenticationRequired - @param exception: reason for generating the header - @rtype: (str, str) - @returns: the header as (part_before_colon, part_after_colon) - """ digest = dict(nonce=self.noncestore.newnonce(), realm=self.gentoken.realm, algorithm="md5", @@ -821,16 +787,3 @@ class AuthDigestMiddleware: digest["stale"] = "TRUE" challenge = format_digest(digest) return ("WWW-Authenticate", "Digest %s" % challenge) - - def authorization_required(self, environ, start_response, exception): - """internal method implementing wsgi interface, serving 401 page""" - status = "401 Not authorized" - headers = [("Content-type", "text/html"), - self.www_authenticate(exception)] - data = "<html><head><title>401 Not authorized</title></head><body><h1>" - data += "401 Not authorized</h1></body></html>" - headers.append(("Content-length", str(len(data)))) - start_response(status, headers) - if environ["REQUEST_METHOD"] == "HEAD": - return [] - return [data] |