summaryrefslogtreecommitdiff
path: root/wsgitools/digest.py
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2011-06-01 15:21:38 +0200
committerHelmut Grohne <helmut@subdivi.de>2011-06-01 15:25:25 +0200
commitfbfd3ddce24a9f86838f64753d8654ddf6c31d81 (patch)
treebb7084eb77ebd6c376c30c48e428fda9ddafb03d /wsgitools/digest.py
parent8603cef047ae1c892f862535ec36da2eb429c2fd (diff)
downloadwsgitools-fbfd3ddce24a9f86838f64753d8654ddf6c31d81.tar.gz
reworked digest.AuthTokenGenerator
AuthTokenGenerator gained a base class AbstractTokenGenerator. This class provides an additional method check_password implementing the interface required by BasicAuthMiddleware. In addition AbstractTokenGenerator gained two subclasses HtdigestTokenGenerator and UpdatingHtdigestTokenGenerator. They both read authentication information from a apache htdigest file. The latter also checks the file for updates.
Diffstat (limited to 'wsgitools/digest.py')
-rwxr-xr-xwsgitools/digest.py124
1 files changed, 117 insertions, 7 deletions
diff --git a/wsgitools/digest.py b/wsgitools/digest.py
index 2a8da05..4b7dd96 100755
--- a/wsgitools/digest.py
+++ b/wsgitools/digest.py
@@ -22,6 +22,7 @@ except ImportError:
import binascii
import base64
import time
+import os
sysrand = random.SystemRandom()
@@ -87,9 +88,50 @@ class AuthenticationRequired(Exception):
not visible to other code.
"""
+__all__.append("AbstractTokenGenerator")
+class AbstractTokenGenerator:
+ """Interface class for generating authentication tokens for
+ L{AuthDigestMiddleware}.
+
+ @ivar realm: is a string according to RFC2617.
+ @type realm: str
+ """
+ def __init__(self, realm):
+ """
+ @type realm: str
+ """
+ self.realm = realm
+
+ def __call__(self, username, algo="md5"):
+ """Generates an authentication token from a username.
+ @type username: str
+ @type algo: str
+ @param algo: currently the only value supported by
+ AuthDigestMiddleware is "md5"
+ @rtype: str or None
+ @returns: a valid token or None to signal that authentication should
+ fail
+ """
+ raise NotImplementedError
+
+ def check_password(self, username, password, environ=None):
+ """
+ This function implements the interface for verifying passwords
+ used by L{BasicAuthMiddleware}. It works by computing a token
+ from the user and comparing it to the token returned by the
+ __call__ method.
+
+ @type username: str
+ @type password: str
+ @param environ: ignored
+ @rtype: bool
+ """
+ token = md5("%s:%s:%s" % (username, self.realm, password)).hexdigest()
+ return token == self(username)
+
__all__.append("AuthTokenGenerator")
-class AuthTokenGenerator:
- """Generates authentification tokens for L{AuthDigestMiddleware}. The
+class AuthTokenGenerator(AbstractTokenGenerator):
+ """Generates authentication tokens for L{AuthDigestMiddleware}. The
interface consists of beeing callable with a username and having a
realm attribute being a string."""
def __init__(self, realm, getpass):
@@ -101,13 +143,10 @@ class AuthTokenGenerator:
expected as result. C{None} may be used as an invalid password.
An example for getpass would be C{{username: password}.get}.
"""
- self.realm = realm
+ AbstractTokenGenerator.__init__(self, realm)
self.getpass = getpass
+
def __call__(self, username, algo="md5"):
- """Generates an authentification token from a username.
- @type username: str
- @rtype: str or None
- """
assert algo.lower() in ["md5", "md5-sess"]
password = self.getpass(username)
if password is None:
@@ -115,6 +154,77 @@ class AuthTokenGenerator:
a1 = "%s:%s:%s" % (username, self.realm, password)
return md5(a1).hexdigest()
+__all__.append("HtdigestTokenGenerator")
+class HtdigestTokenGenerator(AbstractTokenGenerator):
+ """Reads authentication tokens for L{AuthDigestMiddleware from an
+ apache htdigest file.
+ """
+ def __init__(self, realm, htdigestfile, ignoreparseerrors=False):
+ """
+ @type realm: str
+ @type htdigestfile: str
+ @type ignoreparseerrors: bool
+ @param ignoreparseerrors: passed to readhtdigest
+ @raises IOError:
+ @raises ValueError:
+ """
+ AbstractTokenGenerator.__init__(self, realm)
+ self.users = {}
+ self.readhtdigest(htdigestfile, ignoreparseerrors)
+
+ def readhtdigest(self, htdigestfile, ignoreparseerrors=False):
+ """
+ @type htdigestfile: str
+ @type ignoreparseerrors: bool
+ @param ignoreparseerrors: do not raise ValueErrors for bad files
+ @raises IOError:
+ @raises ValueError:
+ """
+ self.users = {}
+ for line in file(htdigestfile):
+ parts = line.rstrip("\n").split(":")
+ if len(parts) != 3:
+ if ignoreparseerrors:
+ continue
+ raise ValueError("invalid number of colons in htdigest file")
+ user, realm, token = parts
+ if realm != self.realm:
+ continue
+ if user in self.users and not ignoreparseerrors:
+ raise ValueError("duplicate user in htdigest file")
+ self.users[user] = token
+
+ def __call__(self, user, algo="md5"):
+ assert algo.lower() in ["md5", "md5-sess"]
+ return self.users.get(user)
+
+__all__.append("UpdatingHtdigestTokenGenerator")
+class UpdatingHtdigestTokenGenerator(HtdigestTokenGenerator):
+ """Behaves like HtdigestTokenGenerator, checks the htdigest file
+ for changes on each invocation.
+ """
+ def __init__(self, realm, htdigestfile, ignoreparseerrors=False):
+ try:
+ self.statcache = os.stat(htdigestfile)
+ except OSError, err:
+ raise IOError(str(err))
+ HtdigestTokenGenerator.__init__(self, realm, htdigestfile,
+ ignoreparseerrors)
+ self.htdigestfile = htdigestfile
+ self.ignoreparseerrors = ignoreparseerrors
+
+ def __call__(self, user, algo="md5"):
+ try:
+ statcache = os.stat(self.htdigestfile)
+ except OSError:
+ return None
+ if self.statcache != statcache:
+ try:
+ self.readhtdigest(self.htdigestfile, self.ignoreparseerrors)
+ except (IOError, ValueError):
+ return None
+ return HtdigestTokenGenerator.__call__(self, user, algo)
+
__all__.append("NonceStoreBase")
class NonceStoreBase:
"""Nonce storage interface."""