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