|
1 # Copyright 2008-2009 LShift Ltd |
|
2 # Author(s): |
|
3 # Paul Crowley <paul@lshift.net> |
|
4 # |
|
5 # This software may be used and distributed according to the terms |
|
6 # of the GNU General Public License, incorporated herein by reference. |
|
7 |
|
8 import sys |
|
9 import re |
|
10 import os |
|
11 import os.path |
|
12 |
|
13 allowedchars = "A-Za-z0-9_-" |
|
14 |
|
15 goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars)) |
|
16 def goodpath(path): |
|
17 return goodpathre.match(path) is not None |
|
18 |
|
19 goodglobre = re.compile("[*/%s]+$" % allowedchars) |
|
20 |
|
21 def goodglob(pattern): |
|
22 return goodglobre.match(pattern) is not None |
|
23 |
|
24 # Don't put anything except *A-Za-z0-9_- in rule globs or |
|
25 # it will match nothing. No regexp metachars, not even . |
|
26 # We may fix this later. |
|
27 def globmatcher(pattern): |
|
28 if not goodglob(pattern): |
|
29 #fail("Bad glob pattern in auth config: %s" % pattern) |
|
30 # FIXME: report it somehow |
|
31 return lambda x: False |
|
32 # Substitution cunning so ** can be different from * |
|
33 pattern = pattern.replace("*", "[]") |
|
34 pattern = pattern.replace("[][]", "[/%s]*" % allowedchars) |
|
35 pattern = pattern.replace("[]", "[%s]*" % allowedchars) |
|
36 rex = re.compile(pattern + "$") |
|
37 # None matches everything |
|
38 return lambda x: x is None or rex.match(x) is not None |
|
39 |
|
40 def rule(pairs): |
|
41 matchers = [(k, globmatcher(v)) for k, v in pairs] |
|
42 def c(kw): |
|
43 for k, m in matchers: |
|
44 if k not in kw or not m(kw[k]): |
|
45 return False |
|
46 return True |
|
47 return c |
|
48 |
|
49 class Ruleset(object): |
|
50 '''Class representing the rules in a rule file''' |
|
51 |
|
52 levels = ["init", "write", "read", "deny"] |
|
53 |
|
54 def __init__(self): |
|
55 self.rules = [] |
|
56 self.preset = {} |
|
57 |
|
58 def add(self, action, conditions): |
|
59 self.rules.append((action, conditions)) |
|
60 |
|
61 def set(self, **kw): |
|
62 self.preset.update(kw) |
|
63 |
|
64 def matchrule(self, kw): |
|
65 d = self.preset.copy() |
|
66 d.update(kw) |
|
67 for a, c in self.rules: |
|
68 if c(d): |
|
69 return a |
|
70 return None |
|
71 |
|
72 def allow(self, level, **kw): |
|
73 a = self.matchrule(kw) |
|
74 return a in self.levels and self.levels.index(a) <= self.levels.index(level) |
|
75 |
|
76 def readfile(self, fn): |
|
77 try: |
|
78 f = open(fn) |
|
79 try: |
|
80 for l in f: |
|
81 l = l.strip() |
|
82 if len(l) == 0 or l.startswith("#"): |
|
83 continue |
|
84 l = l.split() |
|
85 self.add(l[0], rule([c.split("=", 1) for c in l[1:]])) |
|
86 finally: |
|
87 f.close() |
|
88 except Exception, e: |
|
89 print >> sys.stderr, "Failure reading rules file:", e |
|
90 |
|
91 def rules_from_env(): |
|
92 res = Ruleset() |
|
93 for f in os.environ['HG_ACCESS_RULES_PATH'].split(os.pathsep): |
|
94 if os.path.isfile(f): |
|
95 res.readfile(f) |
|
96 return res |