##// END OF EJS Templates
acl: updated doc string to reflect recent changes
Elifarley Callado Coelho Cruz -
r11042:d82f3651 default
parent child Browse files
Show More
@@ -1,121 +1,162 b''
1 # acl.py - changeset access control for mercurial
1 # acl.py - changeset access control for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
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.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''hooks for controlling repository access
8 '''hooks for controlling repository access
9
9
10 This hook makes it possible to allow or deny write access to portions
10 This hook makes it possible to allow or deny write access to portions
11 of a repository when receiving incoming changesets.
11 of a repository when receiving incoming changesets via pretxnchangegroup and
12 pretxncommit.
12
13
13 The authorization is matched based on the local user name on the
14 The authorization is matched based on the local user name on the
14 system where the hook runs, and not the committer of the original
15 system where the hook runs, and not the committer of the original
15 changeset (since the latter is merely informative).
16 changeset (since the latter is merely informative).
16
17
17 The acl hook is best used along with a restricted shell like hgsh,
18 The acl hook is best used along with a restricted shell like hgsh,
18 preventing authenticating users from doing anything other than
19 preventing authenticating users from doing anything other than
19 pushing or pulling. The hook is not safe to use if users have
20 pushing or pulling. The hook is not safe to use if users have
20 interactive shell access, as they can then disable the hook.
21 interactive shell access, as they can then disable the hook.
21 Nor is it safe if remote users share an account, because then there
22 Nor is it safe if remote users share an account, because then there
22 is no way to distinguish them.
23 is no way to distinguish them.
23
24
24 To use this hook, configure the acl extension in your hgrc like this::
25 The deny list is checked before the allow list is.
26
27 The allow and deny sections take key-value pairs, having a subtree pattern
28 as key (with a glob syntax by default). The corresponding value can be either:
29 1) an asterisk, to match everyone;
30 2) a comma-separated list containing users and groups.
31
32 Group names must be prefixed with an @ symbol.
33 Specifying a group name has the same effect as specifying all the users in
34 that group.
35 The set of users for a group is taken from "grp.getgrnam"
36 (see http://docs.python.org/library/grp.html#grp.getgrnam).
37
38 To use this hook, configure the acl extension in your hgrc like this:
25
39
26 [extensions]
40 [extensions]
27 acl =
41 acl =
28
42
29 [hooks]
43 [hooks]
44
45 # Use this if you want to check access restrictions at commit time
46 pretxncommit.acl = python:hgext.acl.hook
47
48 # Use this if you want to check access restrictions for pull, push, bundle
49 # and serve.
30 pretxnchangegroup.acl = python:hgext.acl.hook
50 pretxnchangegroup.acl = python:hgext.acl.hook
31
51
32 [acl]
52 [acl]
33 # Check whether the source of incoming changes is in this list
53 # Check whether the source of incoming changes is in this list
34 # ("serve" == ssh or http, "push", "pull", "bundle")
54 # ("serve" == ssh or http, "push", "pull", "bundle")
35 sources = serve
55 sources = serve
36
56
37 The allow and deny sections take a subtree pattern as key (with a glob
57 [acl.deny]
38 syntax by default), and a comma separated list of users as the
58 # This list is checked first. If a match is found, 'acl.allow' will not be
39 corresponding value. The deny list is checked before the allow list
59 # checked.
40 is. ::
60 # if acl.deny is not present, no users denied by default
61 # empty acl.deny = all users allowed
62 # Format for both lists: glob pattern = user4, user5, @group1
63
64 # To match everyone, use an asterisk for the user:
65 # my/glob/pattern = *
66
67 # user6 will not have write access to any file:
68 ** = user6
69
70 # Group "hg-denied" will not have write access to any file:
71 ** = @hg-denied
72
73 # Nobody will be able to change "DONT-TOUCH-THIS.txt", despite everyone being
74 # able to change all other files. See below.
75 src/main/resources/DONT-TOUCH-THIS.txt = *
41
76
42 [acl.allow]
77 [acl.allow]
43 # If acl.allow is not present, all users are allowed by default.
78 # if acl.allow not present, all users allowed by default
44 # An empty acl.allow section means no users allowed.
79 # empty acl.allow = no users allowed
80
81 # User "doc_writer" has write access to any file under the "docs" folder:
45 docs/** = doc_writer
82 docs/** = doc_writer
83
84 # User "jack" and group "designers" have write access to any file under the
85 # "images" folder:
86 images/** = jack, @designers
87
88 # Everyone (except for "user6" - see "acl.deny" above) will have write access
89 to any file under the "resources" folder (except for 1 file. See "acl.deny"):
90 src/main/resources/** = *
91
46 .hgtags = release_engineer
92 .hgtags = release_engineer
47
93
48 [acl.deny]
49 # If acl.deny is not present, no users are refused by default.
50 # An empty acl.deny section means all users allowed.
51 glob pattern = user4, user5
52 ** = user6
53 '''
94 '''
54
95
55 from mercurial.i18n import _
96 from mercurial.i18n import _
56 from mercurial import util, match
97 from mercurial import util, match
57 import getpass, urllib, grp
98 import getpass, urllib, grp
58
99
59 def _getusers(group):
100 def _getusers(group):
60 return grp.getgrnam(group).gr_mem
101 return grp.getgrnam(group).gr_mem
61
102
62 def _usermatch(user, usersorgroups):
103 def _usermatch(user, usersorgroups):
63
104
64 if usersorgroups == '*':
105 if usersorgroups == '*':
65 return True
106 return True
66
107
67 for ug in usersorgroups.replace(',', ' ').split():
108 for ug in usersorgroups.replace(',', ' ').split():
68 if user == ug or ug.find('@') == 0 and user in _getusers(ug[1:]):
109 if user == ug or ug.find('@') == 0 and user in _getusers(ug[1:]):
69 return True
110 return True
70
111
71 return False
112 return False
72
113
73 def buildmatch(ui, repo, user, key):
114 def buildmatch(ui, repo, user, key):
74 '''return tuple of (match function, list enabled).'''
115 '''return tuple of (match function, list enabled).'''
75 if not ui.has_section(key):
116 if not ui.has_section(key):
76 ui.debug('acl: %s not enabled\n' % key)
117 ui.debug('acl: %s not enabled\n' % key)
77 return None
118 return None
78
119
79 pats = [pat for pat, users in ui.configitems(key)
120 pats = [pat for pat, users in ui.configitems(key)
80 if _usermatch(user, users)]
121 if _usermatch(user, users)]
81 ui.debug('acl: %s enabled, %d entries for user %s\n' %
122 ui.debug('acl: %s enabled, %d entries for user %s\n' %
82 (key, len(pats), user))
123 (key, len(pats), user))
83 if pats:
124 if pats:
84 return match.match(repo.root, '', pats)
125 return match.match(repo.root, '', pats)
85 return match.exact(repo.root, '', [])
126 return match.exact(repo.root, '', [])
86
127
87
128
88 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
129 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
89 if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
130 if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
90 raise util.Abort(_('config error - hook type "%s" cannot stop '
131 raise util.Abort(_('config error - hook type "%s" cannot stop '
91 'incoming changesets nor commits') % hooktype)
132 'incoming changesets nor commits') % hooktype)
92 if (hooktype == 'pretxnchangegroup' and
133 if (hooktype == 'pretxnchangegroup' and
93 source not in ui.config('acl', 'sources', 'serve').split()):
134 source not in ui.config('acl', 'sources', 'serve').split()):
94 ui.debug('acl: changes have source "%s" - skipping\n' % source)
135 ui.debug('acl: changes have source "%s" - skipping\n' % source)
95 return
136 return
96
137
97 user = None
138 user = None
98 if source == 'serve' and 'url' in kwargs:
139 if source == 'serve' and 'url' in kwargs:
99 url = kwargs['url'].split(':')
140 url = kwargs['url'].split(':')
100 if url[0] == 'remote' and url[1].startswith('http'):
141 if url[0] == 'remote' and url[1].startswith('http'):
101 user = urllib.unquote(url[3])
142 user = urllib.unquote(url[3])
102
143
103 if user is None:
144 if user is None:
104 user = getpass.getuser()
145 user = getpass.getuser()
105
146
106 cfg = ui.config('acl', 'config')
147 cfg = ui.config('acl', 'config')
107 if cfg:
148 if cfg:
108 ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
149 ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
109 allow = buildmatch(ui, repo, user, 'acl.allow')
150 allow = buildmatch(ui, repo, user, 'acl.allow')
110 deny = buildmatch(ui, repo, user, 'acl.deny')
151 deny = buildmatch(ui, repo, user, 'acl.deny')
111
152
112 for rev in xrange(repo[node], len(repo)):
153 for rev in xrange(repo[node], len(repo)):
113 ctx = repo[rev]
154 ctx = repo[rev]
114 for f in ctx.files():
155 for f in ctx.files():
115 if deny and deny(f):
156 if deny and deny(f):
116 ui.debug('acl: user %s denied on %s\n' % (user, f))
157 ui.debug('acl: user %s denied on %s\n' % (user, f))
117 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
158 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
118 if allow and not allow(f):
159 if allow and not allow(f):
119 ui.debug('acl: user %s not allowed on %s\n' % (user, f))
160 ui.debug('acl: user %s not allowed on %s\n' % (user, f))
120 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
161 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
121 ui.debug('acl: allowing changeset %s\n' % ctx)
162 ui.debug('acl: allowing changeset %s\n' % ctx)
General Comments 0
You need to be logged in to leave comments. Login now