summaryrefslogtreecommitdiff
path: root/wsgitools/digest.py
diff options
context:
space:
mode:
Diffstat (limited to 'wsgitools/digest.py')
-rw-r--r--wsgitools/digest.py143
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]