Commit | Line | Data |
---|---|---|
ecbfa279 FT |
1 | import binascii, hashlib, threading, time |
2 | import resp | |
3 | ||
4 | class 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 | ||
16 | class forbidden(resp.httperror): | |
17 | def __init__(self, message=None, detail=None): | |
18 | super(forbidden, self).__init__(403, message, detail) | |
19 | ||
20 | def 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 | ||
29 | def parsebasic(req): | |
30 | mech, data = parsemech(req) | |
31 | if mech != "basic": | |
32 | return None, None | |
33 | try: | |
34 | raw = binascii.a2b_base64(data) | |
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 | ||
42 | class 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") |