Merge branch 'master' into python2
[wrw.git] / wrw / auth.py
CommitLineData
ecbfa279 1import binascii, hashlib, threading, time
b6f62b89 2import resp, proto
ecbfa279
FT
3
4class unauthorized(resp.httperror):
5 def __init__(self, challenge, message=None, detail=None):
6 super(unauthorized, self).__init__(401, message, detail)
7 if isinstance(challenge, str):
8 challenge = [challenge]
9 self.challenge = challenge
10
11 def handle(self, req):
12 for challenge in self.challenge:
13 req.ohead.add("WWW-Authenticate", challenge)
14 return super(unauthorized, self).handle(req)
15
16class forbidden(resp.httperror):
17 def __init__(self, message=None, detail=None):
18 super(forbidden, self).__init__(403, message, detail)
19
20def parsemech(req):
21 h = req.ihead.get("Authorization", None)
22 if h is None:
23 return None, None
24 p = h.find(" ")
25 if p < 0:
26 return None, None
27 return h[:p].strip().lower(), h[p + 1:].strip()
28
29def parsebasic(req):
30 mech, data = parsemech(req)
31 if mech != "basic":
32 return None, None
33 try:
c4b97e16 34 raw = proto.unb64(data)
ecbfa279
FT
35 except binascii.Error:
36 return None, None
37 p = raw.find(":")
38 if p < 0:
39 return None, None
40 return raw[:p], raw[p + 1:]
41
42class basiccache(object):
43 cachetime = 300
44
45 def __init__(self, realm, authfn=None):
46 self._lock = threading.Lock()
47 self._cache = {}
48 self.realm = realm
49 if authfn is not None:
50 self.auth = authfn
51
52 def _obscure(self, nm, pw):
53 dig = hashlib.sha256()
54 dig.update(self.realm)
55 dig.update(nm)
56 dig.update(pw)
57 return dig.digest()
58
59 def check(self, req):
60 nm, pw = parsebasic(req)
61 if nm is None:
62 raise unauthorized("Basic Realm=\"%s\"" % self.realm)
63 pwh = self._obscure(nm, pw)
64 now = time.time()
65 with self._lock:
66 if (nm, pwh) in self._cache:
67 lock, atime, res, resob = self._cache[nm, pwh]
68 if now - atime < self.cachetime:
69 if res == "s":
70 return resob
71 elif res == "f":
72 raise resob
73 else:
74 lock = threading.Lock()
75 self._cache[nm, pwh] = (lock, now, None, None)
76 with lock:
77 try:
78 ret = self.auth(req, nm, pw)
79 except forbidden, exc:
80 with self._lock:
81 self._cache[nm, pwh] = (lock, now, "f", exc)
82 raise
83 if ret is None:
84 raise forbidden()
85 with self._lock:
86 self._cache[nm, pwh] = (lock, now, "s", ret)
87 return ret
88
89 def auth(self, req, nm, pw):
90 raise Exception("authentication function neither supplied nor overridden")