Added a new module to ease WWW-authentication a bit.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 3 Nov 2013 14:09:40 +0000 (15:09 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 3 Nov 2013 14:09:40 +0000 (15:09 +0100)
wrw/auth.py [new file with mode: 0644]

diff --git a/wrw/auth.py b/wrw/auth.py
new file mode 100644 (file)
index 0000000..4ae292d
--- /dev/null
@@ -0,0 +1,90 @@
+import binascii, hashlib, threading, time
+import resp
+
+class unauthorized(resp.httperror):
+    def __init__(self, challenge, message=None, detail=None):
+        super(unauthorized, self).__init__(401, message, detail)
+        if isinstance(challenge, str):
+            challenge = [challenge]
+        self.challenge = challenge
+
+    def handle(self, req):
+        for challenge in self.challenge:
+            req.ohead.add("WWW-Authenticate", challenge)
+        return super(unauthorized, self).handle(req)
+
+class forbidden(resp.httperror):
+    def __init__(self, message=None, detail=None):
+        super(forbidden, self).__init__(403, message, detail)
+
+def parsemech(req):
+    h = req.ihead.get("Authorization", None)
+    if h is None:
+        return None, None
+    p = h.find(" ")
+    if p < 0:
+        return None, None
+    return h[:p].strip().lower(), h[p + 1:].strip()
+
+def parsebasic(req):
+    mech, data = parsemech(req)
+    if mech != "basic":
+        return None, None
+    try:
+        raw = binascii.a2b_base64(data)
+    except binascii.Error:
+        return None, None
+    p = raw.find(":")
+    if p < 0:
+        return None, None
+    return raw[:p], raw[p + 1:]
+
+class basiccache(object):
+    cachetime = 300
+
+    def __init__(self, realm, authfn=None):
+        self._lock = threading.Lock()
+        self._cache = {}
+        self.realm = realm
+        if authfn is not None:
+            self.auth = authfn
+
+    def _obscure(self, nm, pw):
+        dig = hashlib.sha256()
+        dig.update(self.realm)
+        dig.update(nm)
+        dig.update(pw)
+        return dig.digest()
+
+    def check(self, req):
+        nm, pw = parsebasic(req)
+        if nm is None:
+            raise unauthorized("Basic Realm=\"%s\"" % self.realm)
+        pwh = self._obscure(nm, pw)
+        now = time.time()
+        with self._lock:
+            if (nm, pwh) in self._cache:
+                lock, atime, res, resob = self._cache[nm, pwh]
+                if now - atime < self.cachetime:
+                    if res == "s":
+                        return resob
+                    elif res == "f":
+                        raise resob
+            else:
+                lock = threading.Lock()
+                self._cache[nm, pwh] = (lock, now, None, None)
+        with lock:
+            try:
+                ret = self.auth(req, nm, pw)
+            except forbidden, exc:
+                with self._lock:
+                    self._cache[nm, pwh] = (lock, now, "f", exc)
+                raise
+            if ret is None:
+                raise forbidden()
+            with self._lock:
+                self._cache[nm, pwh] = (lock, now, "s", ret)
+            return ret
+
+    def auth(self, req, nm, pw):
+        raise Exception("authentication function neither supplied nor overridden")