eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/acl.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # acl.py - changeset access control for mercurial
       
     2 #
       
     3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 '''hooks for controlling repository access
       
     9 
       
    10 This hook makes it possible to allow or deny write access to given
       
    11 branches and paths of a repository when receiving incoming changesets
       
    12 via pretxnchangegroup and pretxncommit.
       
    13 
       
    14 The authorization is matched based on the local user name on the
       
    15 system where the hook runs, and not the committer of the original
       
    16 changeset (since the latter is merely informative).
       
    17 
       
    18 The acl hook is best used along with a restricted shell like hgsh,
       
    19 preventing authenticating users from doing anything other than pushing
       
    20 or pulling. The hook is not safe to use if users have interactive
       
    21 shell access, as they can then disable the hook. Nor is it safe if
       
    22 remote users share an account, because then there is no way to
       
    23 distinguish them.
       
    24 
       
    25 The order in which access checks are performed is:
       
    26 
       
    27 1) Deny  list for branches (section ``acl.deny.branches``)
       
    28 2) Allow list for branches (section ``acl.allow.branches``)
       
    29 3) Deny  list for paths    (section ``acl.deny``)
       
    30 4) Allow list for paths    (section ``acl.allow``)
       
    31 
       
    32 The allow and deny sections take key-value pairs.
       
    33 
       
    34 Branch-based Access Control
       
    35 ...........................
       
    36 
       
    37 Use the ``acl.deny.branches`` and ``acl.allow.branches`` sections to
       
    38 have branch-based access control. Keys in these sections can be
       
    39 either:
       
    40 
       
    41 - a branch name, or
       
    42 - an asterisk, to match any branch;
       
    43 
       
    44 The corresponding values can be either:
       
    45 
       
    46 - a comma-separated list containing users and groups, or
       
    47 - an asterisk, to match anyone;
       
    48 
       
    49 Path-based Access Control
       
    50 .........................
       
    51 
       
    52 Use the ``acl.deny`` and ``acl.allow`` sections to have path-based
       
    53 access control. Keys in these sections accept a subtree pattern (with
       
    54 a glob syntax by default). The corresponding values follow the same
       
    55 syntax as the other sections above.
       
    56 
       
    57 Groups
       
    58 ......
       
    59 
       
    60 Group names must be prefixed with an ``@`` symbol. Specifying a group
       
    61 name has the same effect as specifying all the users in that group.
       
    62 
       
    63 You can define group members in the ``acl.groups`` section.
       
    64 If a group name is not defined there, and Mercurial is running under
       
    65 a Unix-like system, the list of users will be taken from the OS.
       
    66 Otherwise, an exception will be raised.
       
    67 
       
    68 Example Configuration
       
    69 .....................
       
    70 
       
    71 ::
       
    72 
       
    73   [hooks]
       
    74 
       
    75   # Use this if you want to check access restrictions at commit time
       
    76   pretxncommit.acl = python:hgext.acl.hook
       
    77 
       
    78   # Use this if you want to check access restrictions for pull, push,
       
    79   # bundle and serve.
       
    80   pretxnchangegroup.acl = python:hgext.acl.hook
       
    81 
       
    82   [acl]
       
    83   # Allow or deny access for incoming changes only if their source is
       
    84   # listed here, let them pass otherwise. Source is "serve" for all
       
    85   # remote access (http or ssh), "push", "pull" or "bundle" when the
       
    86   # related commands are run locally.
       
    87   # Default: serve
       
    88   sources = serve
       
    89 
       
    90   [acl.deny.branches]
       
    91 
       
    92   # Everyone is denied to the frozen branch:
       
    93   frozen-branch = *
       
    94 
       
    95   # A bad user is denied on all branches:
       
    96   * = bad-user
       
    97 
       
    98   [acl.allow.branches]
       
    99 
       
   100   # A few users are allowed on branch-a:
       
   101   branch-a = user-1, user-2, user-3
       
   102 
       
   103   # Only one user is allowed on branch-b:
       
   104   branch-b = user-1
       
   105 
       
   106   # The super user is allowed on any branch:
       
   107   * = super-user
       
   108 
       
   109   # Everyone is allowed on branch-for-tests:
       
   110   branch-for-tests = *
       
   111 
       
   112   [acl.deny]
       
   113   # This list is checked first. If a match is found, acl.allow is not
       
   114   # checked. All users are granted access if acl.deny is not present.
       
   115   # Format for both lists: glob pattern = user, ..., @group, ...
       
   116 
       
   117   # To match everyone, use an asterisk for the user:
       
   118   # my/glob/pattern = *
       
   119 
       
   120   # user6 will not have write access to any file:
       
   121   ** = user6
       
   122 
       
   123   # Group "hg-denied" will not have write access to any file:
       
   124   ** = @hg-denied
       
   125 
       
   126   # Nobody will be able to change "DONT-TOUCH-THIS.txt", despite
       
   127   # everyone being able to change all other files. See below.
       
   128   src/main/resources/DONT-TOUCH-THIS.txt = *
       
   129 
       
   130   [acl.allow]
       
   131   # if acl.allow is not present, all users are allowed by default
       
   132   # empty acl.allow = no users allowed
       
   133 
       
   134   # User "doc_writer" has write access to any file under the "docs"
       
   135   # folder:
       
   136   docs/** = doc_writer
       
   137 
       
   138   # User "jack" and group "designers" have write access to any file
       
   139   # under the "images" folder:
       
   140   images/** = jack, @designers
       
   141 
       
   142   # Everyone (except for "user6" - see acl.deny above) will have write
       
   143   # access to any file under the "resources" folder (except for 1
       
   144   # file. See acl.deny):
       
   145   src/main/resources/** = *
       
   146 
       
   147   .hgtags = release_engineer
       
   148 
       
   149 '''
       
   150 
       
   151 from mercurial.i18n import _
       
   152 from mercurial import util, match
       
   153 import getpass, urllib
       
   154 
       
   155 def _getusers(ui, group):
       
   156 
       
   157     # First, try to use group definition from section [acl.groups]
       
   158     hgrcusers = ui.configlist('acl.groups', group)
       
   159     if hgrcusers:
       
   160         return hgrcusers
       
   161 
       
   162     ui.debug('acl: "%s" not defined in [acl.groups]\n' % group)
       
   163     # If no users found in group definition, get users from OS-level group
       
   164     try:
       
   165         return util.groupmembers(group)
       
   166     except KeyError:
       
   167         raise util.Abort(_("group '%s' is undefined") % group)
       
   168 
       
   169 def _usermatch(ui, user, usersorgroups):
       
   170 
       
   171     if usersorgroups == '*':
       
   172         return True
       
   173 
       
   174     for ug in usersorgroups.replace(',', ' ').split():
       
   175         if user == ug or ug.find('@') == 0 and user in _getusers(ui, ug[1:]):
       
   176             return True
       
   177 
       
   178     return False
       
   179 
       
   180 def buildmatch(ui, repo, user, key):
       
   181     '''return tuple of (match function, list enabled).'''
       
   182     if not ui.has_section(key):
       
   183         ui.debug('acl: %s not enabled\n' % key)
       
   184         return None
       
   185 
       
   186     pats = [pat for pat, users in ui.configitems(key)
       
   187             if _usermatch(ui, user, users)]
       
   188     ui.debug('acl: %s enabled, %d entries for user %s\n' %
       
   189              (key, len(pats), user))
       
   190 
       
   191     if not repo:
       
   192         if pats:
       
   193             return lambda b: '*' in pats or b in pats
       
   194         return lambda b: False
       
   195 
       
   196     if pats:
       
   197         return match.match(repo.root, '', pats)
       
   198     return match.exact(repo.root, '', [])
       
   199 
       
   200 
       
   201 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
       
   202     if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
       
   203         raise util.Abort(_('config error - hook type "%s" cannot stop '
       
   204                            'incoming changesets nor commits') % hooktype)
       
   205     if (hooktype == 'pretxnchangegroup' and
       
   206         source not in ui.config('acl', 'sources', 'serve').split()):
       
   207         ui.debug('acl: changes have source "%s" - skipping\n' % source)
       
   208         return
       
   209 
       
   210     user = None
       
   211     if source == 'serve' and 'url' in kwargs:
       
   212         url = kwargs['url'].split(':')
       
   213         if url[0] == 'remote' and url[1].startswith('http'):
       
   214             user = urllib.unquote(url[3])
       
   215 
       
   216     if user is None:
       
   217         user = getpass.getuser()
       
   218 
       
   219     cfg = ui.config('acl', 'config')
       
   220     if cfg:
       
   221         ui.readconfig(cfg, sections = ['acl.groups', 'acl.allow.branches',
       
   222         'acl.deny.branches', 'acl.allow', 'acl.deny'])
       
   223 
       
   224     allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
       
   225     denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
       
   226     allow = buildmatch(ui, repo, user, 'acl.allow')
       
   227     deny = buildmatch(ui, repo, user, 'acl.deny')
       
   228 
       
   229     for rev in xrange(repo[node], len(repo)):
       
   230         ctx = repo[rev]
       
   231         branch = ctx.branch()
       
   232         if denybranches and denybranches(branch):
       
   233             raise util.Abort(_('acl: user "%s" denied on branch "%s"'
       
   234                                ' (changeset "%s")')
       
   235                                % (user, branch, ctx))
       
   236         if allowbranches and not allowbranches(branch):
       
   237             raise util.Abort(_('acl: user "%s" not allowed on branch "%s"'
       
   238                                ' (changeset "%s")')
       
   239                                % (user, branch, ctx))
       
   240         ui.debug('acl: branch access granted: "%s" on branch "%s"\n'
       
   241         % (ctx, branch))
       
   242 
       
   243         for f in ctx.files():
       
   244             if deny and deny(f):
       
   245                 ui.debug('acl: user %s denied on %s\n' % (user, f))
       
   246                 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
       
   247             if allow and not allow(f):
       
   248                 ui.debug('acl: user %s not allowed on %s\n' % (user, f))
       
   249                 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
       
   250         ui.debug('acl: allowing changeset %s\n' % ctx)