1 # Copyright 2008 Paul Crowley <paul@lshift.net> |
1 # Copyright 2008 LShift Ltd |
2 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
2 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
3 # |
|
4 # Authors: |
|
5 # Paul Crowley <paul@lshift.net> |
|
6 # Vadim Gelfer <vadim.gelfer@gmail.com> |
3 # |
7 # |
4 # This software may be used and distributed according to the terms |
8 # This software may be used and distributed according to the terms |
5 # of the GNU General Public License, incorporated herein by reference. |
9 # of the GNU General Public License, incorporated herein by reference. |
6 |
10 |
7 from mercurial.i18n import _ |
11 from mercurial.i18n import _ |
8 from mercurial.node import * |
12 from mercurial.node import * |
9 from mercurial import util |
13 from mercurial import util |
10 |
14 |
11 import os |
15 import os |
12 import re |
16 import rules |
13 |
|
14 allowedchars = "A-Za-z0-9_-" |
|
15 goodglobre = re.compile("[*/%s]+$" % allowedchars) |
|
16 |
|
17 # Don't put anything except *A-Za-z0-9_- in rule globs or |
|
18 # it will match nothing. No regexp metachars, not even . |
|
19 # We may fix this later. |
|
20 def globmatcher(pattern): |
|
21 if not goodglobre.match(pattern): |
|
22 #fail("Bad glob pattern in auth config: %s" % pattern) |
|
23 # FIXME: report it somehow |
|
24 return lambda x: False |
|
25 pattern = pattern.replace("*", "[]") |
|
26 pattern = pattern.replace("[][]", "[/%s]*" % allowedchars) |
|
27 pattern = pattern.replace("[]", "[%s]*" % allowedchars) |
|
28 rex = re.compile(pattern + "$") |
|
29 return lambda x: rex.match(x) is not None |
|
30 |
|
31 def rule(pairs): |
|
32 matchers = [(k, globmatcher(v)) for k, v in pairs] |
|
33 def c(**kw): |
|
34 for k, m in matchers: |
|
35 if k not in kw or not m(kw[k]): |
|
36 return False |
|
37 return True |
|
38 return c |
|
39 |
|
40 class Rulefile(object): |
|
41 '''Class representing the rules in a rule file''' |
|
42 |
|
43 self.levels = ["init", "write", "read", "deny"] |
|
44 |
|
45 def __init__(self): |
|
46 self.rules = [] |
|
47 |
|
48 def add(self, action, conditions): |
|
49 self.rules.append((action, conditions)) |
|
50 |
|
51 def matchrule(self, **kw): |
|
52 for a, c in self.rules: |
|
53 if c(**kw): |
|
54 return a |
|
55 return None |
|
56 |
|
57 def allow(self, level, **kw): |
|
58 a = matchrule(self, **kw) |
|
59 return a in self.levels and self.levels.index(a) <= self.levels.index(level) |
|
60 |
|
61 def read_rules(fn): |
|
62 res = Rulefile() |
|
63 f = open(fn) |
|
64 try: |
|
65 for l in f: |
|
66 l = l.strip() |
|
67 if len(l) == 0 or l.startswith["#"]: |
|
68 continue |
|
69 res.add(l[0], rule([c.split("=", 1) for c in l[1:]])) |
|
70 finally: |
|
71 f.close() |
|
72 |
17 |
73 class Checker(object): |
18 class Checker(object): |
74 '''acl checker.''' |
19 '''acl checker.''' |
75 |
20 |
76 def getuser(self): |
|
77 '''return name of hg-ssh user''' |
|
78 return os.environ['REMOTE_USER'] |
|
79 |
|
80 def __init__(self, ui, repo): |
21 def __init__(self, ui, repo): |
81 self.ui = ui |
22 self.ui = ui |
82 self.repo = repo |
23 self.repo = repo |
83 self.rules = read_rules(os.environ['HG_ACCESS_RULES_FILE']) |
24 self.repo_path = os.environ['HG_REPO_PATH'] |
|
25 self.user = os.environ['REMOTE_USER'] |
|
26 self.rules = rules.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE']) |
84 |
27 |
85 def check(self, node): |
28 def check(self, node): |
86 '''return if access allowed, raise exception if not.''' |
29 '''return if access allowed, raise exception if not.''' |
87 files = self.repo.changectx(node).files() |
30 files = self.repo.changectx(node).files() |
88 for f in files: |
31 for f in files: |
89 if not self.rules.allow("write", user=self.user, ): |
32 if not self.rules.allow("write", user=self.user, repo=self.repo_path, file=f): |
90 self.ui.debug(_('%s: user %s not allowed on %s\n') % |
33 self.ui.debug(_('%s: user %s not allowed on %s\n') % |
91 (__name__, self.getuser(), f)) |
34 (__name__, self.getuser(), f)) |
92 raise util.Abort(_('%s: access denied for changeset %s') % |
35 raise util.Abort(_('%s: access denied for changeset %s') % |
93 (__name__, short(node))) |
36 (__name__, short(node))) |
94 self.ui.debug(_('%s: allowing changeset %s\n') % (__name__, short(node))) |
37 self.ui.debug(_('%s: allowing changeset %s\n') % (__name__, short(node))) |
95 |
38 |
96 def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
39 def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
97 if hooktype != 'pretxnchangegroup': |
40 if hooktype != 'pretxnchangegroup': |
98 raise util.Abort(_('config error - hook type "%s" cannot stop ' |
41 raise util.Abort(_('config error - hook type "%s" cannot stop ' |
99 'incoming changesets') % hooktype) |
42 'incoming changesets') % hooktype) |
100 |
43 c = Checker(ui, repo) |
101 c = checker(ui, repo) |
|
102 |
|
103 start = repo.changelog.rev(bin(node)) |
44 start = repo.changelog.rev(bin(node)) |
104 end = repo.changelog.count() |
45 end = repo.changelog.count() |
105 for rev in xrange(start, end): |
46 for rev in xrange(start, end): |
106 c.check(repo.changelog.node(rev)) |
47 c.check(repo.changelog.node(rev)) |
107 |
48 |