access.py
changeset 18 538d6b198f4a
parent 17 4c98440de851
child 19 62ee928ac9b3
child 20 f4daa224dc7e
equal deleted inserted replaced
17:4c98440de851 18:538d6b198f4a
     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