access.py
changeset 17 4c98440de851
child 18 538d6b198f4a
equal deleted inserted replaced
16:9fac559c3d55 17:4c98440de851
       
     1 # Copyright 2008 Paul Crowley <paul@lshift.net>
       
     2 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
       
     3 #
       
     4 # This software may be used and distributed according to the terms
       
     5 # of the GNU General Public License, incorporated herein by reference.
       
     6 
       
     7 from mercurial.i18n import _
       
     8 from mercurial.node import *
       
     9 from mercurial import util
       
    10 
       
    11 import os
       
    12 import re
       
    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 
       
    73 class Checker(object):
       
    74     '''acl checker.'''
       
    75 
       
    76     def getuser(self):
       
    77         '''return name of hg-ssh user'''
       
    78         return os.environ['REMOTE_USER']
       
    79 
       
    80     def __init__(self, ui, repo):
       
    81         self.ui = ui
       
    82         self.repo = repo
       
    83         self.rules = read_rules(os.environ['HG_ACCESS_RULES_FILE'])
       
    84 
       
    85     def check(self, node):
       
    86         '''return if access allowed, raise exception if not.'''
       
    87         files = self.repo.changectx(node).files()
       
    88         for f in files:
       
    89             if not self.rules.allow("write", user=self.user, ):
       
    90                 self.ui.debug(_('%s: user %s not allowed on %s\n') %
       
    91                               (__name__, self.getuser(), f))
       
    92                 raise util.Abort(_('%s: access denied for changeset %s') %
       
    93                                  (__name__, short(node)))
       
    94         self.ui.debug(_('%s: allowing changeset %s\n') % (__name__, short(node)))
       
    95 
       
    96 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
       
    97     if hooktype != 'pretxnchangegroup':
       
    98         raise util.Abort(_('config error - hook type "%s" cannot stop '
       
    99                            'incoming changesets') % hooktype)
       
   100 
       
   101     c = checker(ui, repo)
       
   102 
       
   103     start = repo.changelog.rev(bin(node))
       
   104     end = repo.changelog.count()
       
   105     for rev in xrange(start, end):
       
   106         c.check(repo.changelog.node(rev))
       
   107