##// END OF EJS Templates
acl: support for group definitions in section [acl.groups], which take precedence over OS-level groups
Elifarley Callado Coelho Cruz -
r11114:62714143 default
parent child Browse files
Show More
@@ -1,232 +1,240
1 1 # acl.py - changeset access control for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''hooks for controlling repository access
9 9
10 10 This hook makes it possible to allow or deny write access to given
11 11 branches and paths of a repository when receiving incoming changesets
12 12 via pretxnchangegroup and pretxncommit.
13 13
14 14 The authorization is matched based on the local user name on the
15 15 system where the hook runs, and not the committer of the original
16 16 changeset (since the latter is merely informative).
17 17
18 18 The acl hook is best used along with a restricted shell like hgsh,
19 19 preventing authenticating users from doing anything other than pushing
20 20 or pulling. The hook is not safe to use if users have interactive
21 21 shell access, as they can then disable the hook. Nor is it safe if
22 22 remote users share an account, because then there is no way to
23 23 distinguish them.
24 24
25 25 The order in which access checks are performed is:
26 26
27 27 1) Deny list for branches (section ``acl.deny.branches``)
28 28 2) Allow list for branches (section ``acl.allow.branches``)
29 29 3) Deny list for paths (section ``acl.deny``)
30 30 4) Allow list for paths (section ``acl.allow``)
31 31
32 32 The allow and deny sections take key-value pairs.
33 33
34 34 Branch-based Access Control
35 35 ---------------------------
36 36
37 37 Use the ``acl.deny.branches`` and ``acl.allow.branches`` sections to
38 38 have branch-based access control. Keys in these sections can be
39 39 either:
40 40
41 41 - a branch name, or
42 42 - an asterisk, to match any branch;
43 43
44 44 The corresponding values can be either:
45 45
46 46 - a comma-separated list containing users and groups, or
47 47 - an asterisk, to match anyone;
48 48
49 49 Path-based Access Control
50 50 -------------------------
51 51
52 52 Use the ``acl.deny`` and ``acl.allow`` sections to have path-based
53 53 access control. Keys in these sections accept a subtree pattern (with
54 54 a glob syntax by default). The corresponding values follow the same
55 55 syntax as the other sections above.
56 56
57 57 Groups
58 58 ------
59 59
60 60 Group names must be prefixed with an ``@`` symbol. Specifying a group
61 61 name has the same effect as specifying all the users in that group.
62 62
63 63 Example Configuration
64 64 ---------------------
65 65
66 66 ::
67 67
68 68 [hooks]
69 69
70 70 # Use this if you want to check access restrictions at commit time
71 71 pretxncommit.acl = python:hgext.acl.hook
72 72
73 73 # Use this if you want to check access restrictions for pull, push,
74 74 # bundle and serve.
75 75 pretxnchangegroup.acl = python:hgext.acl.hook
76 76
77 77 [acl]
78 78 # Check whether the source of incoming changes is in this list where
79 79 # "serve" == ssh or http, and "push", "pull" and "bundle" are the
80 80 # corresponding hg commands.
81 81 sources = serve
82 82
83 83 [acl.deny.branches]
84 84
85 85 # Everyone is denied to the frozen branch:
86 86 frozen-branch = *
87 87
88 88 # A bad user is denied on all branches:
89 89 * = bad-user
90 90
91 91 [acl.allow.branches]
92 92
93 93 # A few users are allowed on branch-a:
94 94 branch-a = user-1, user-2, user-3
95 95
96 96 # Only one user is allowed on branch-b:
97 97 branch-b = user-1
98 98
99 99 # The super user is allowed on any branch:
100 100 * = super-user
101 101
102 102 # Everyone is allowed on branch-for-tests:
103 103 branch-for-tests = *
104 104
105 105 [acl.deny]
106 106 # This list is checked first. If a match is found, acl.allow is not
107 107 # checked. All users are granted access if acl.deny is not present.
108 108 # Format for both lists: glob pattern = user, ..., @group, ...
109 109
110 110 # To match everyone, use an asterisk for the user:
111 111 # my/glob/pattern = *
112 112
113 113 # user6 will not have write access to any file:
114 114 ** = user6
115 115
116 116 # Group "hg-denied" will not have write access to any file:
117 117 ** = @hg-denied
118 118
119 119 # Nobody will be able to change "DONT-TOUCH-THIS.txt", despite
120 120 # everyone being able to change all other files. See below.
121 121 src/main/resources/DONT-TOUCH-THIS.txt = *
122 122
123 123 [acl.allow]
124 124 # if acl.allow not present, all users allowed by default
125 125 # empty acl.allow = no users allowed
126 126
127 127 # User "doc_writer" has write access to any file under the "docs"
128 128 # folder:
129 129 docs/** = doc_writer
130 130
131 131 # User "jack" and group "designers" have write access to any file
132 132 # under the "images" folder:
133 133 images/** = jack, @designers
134 134
135 135 # Everyone (except for "user6" - see acl.deny above) will have write
136 136 # access to any file under the "resources" folder (except for 1
137 137 # file. See acl.deny):
138 138 src/main/resources/** = *
139 139
140 140 .hgtags = release_engineer
141 141
142 142 '''
143 143
144 144 from mercurial.i18n import _
145 145 from mercurial import util, match
146 146 import getpass, urllib, grp
147 147
148 def _getusers(group):
148 def _getusers(ui, group):
149
150 # First, try to use group definition from section [acl.groups]
151 hgrcusers = ui.configlist('acl.groups', group)
152 if hgrcusers:
153 return hgrcusers
154
155 ui.debug('acl: "%s" not defined in [acl.groups]\n' % group)
156 # If no users found in group definition, get users from OS-level group
149 157 return grp.getgrnam(group).gr_mem
150 158
151 def _usermatch(user, usersorgroups):
159 def _usermatch(ui, user, usersorgroups):
152 160
153 161 if usersorgroups == '*':
154 162 return True
155 163
156 164 for ug in usersorgroups.replace(',', ' ').split():
157 if user == ug or ug.find('@') == 0 and user in _getusers(ug[1:]):
165 if user == ug or ug.find('@') == 0 and user in _getusers(ui, ug[1:]):
158 166 return True
159 167
160 168 return False
161 169
162 170 def buildmatch(ui, repo, user, key):
163 171 '''return tuple of (match function, list enabled).'''
164 172 if not ui.has_section(key):
165 173 ui.debug('acl: %s not enabled\n' % key)
166 174 return None
167 175
168 176 pats = [pat for pat, users in ui.configitems(key)
169 if _usermatch(user, users)]
177 if _usermatch(ui, user, users)]
170 178 ui.debug('acl: %s enabled, %d entries for user %s\n' %
171 179 (key, len(pats), user))
172 180
173 181 if not repo:
174 182 if pats:
175 183 return lambda b: '*' in pats or b in pats
176 184 return lambda b: False
177 185
178 186 if pats:
179 187 return match.match(repo.root, '', pats)
180 188 return match.exact(repo.root, '', [])
181 189
182 190
183 191 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
184 192 if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
185 193 raise util.Abort(_('config error - hook type "%s" cannot stop '
186 194 'incoming changesets nor commits') % hooktype)
187 195 if (hooktype == 'pretxnchangegroup' and
188 196 source not in ui.config('acl', 'sources', 'serve').split()):
189 197 ui.debug('acl: changes have source "%s" - skipping\n' % source)
190 198 return
191 199
192 200 user = None
193 201 if source == 'serve' and 'url' in kwargs:
194 202 url = kwargs['url'].split(':')
195 203 if url[0] == 'remote' and url[1].startswith('http'):
196 204 user = urllib.unquote(url[3])
197 205
198 206 if user is None:
199 207 user = getpass.getuser()
200 208
201 209 cfg = ui.config('acl', 'config')
202 210 if cfg:
203 ui.readconfig(cfg, sections = ['acl.allow.branches',
211 ui.readconfig(cfg, sections = ['acl.groups', 'acl.allow.branches',
204 212 'acl.deny.branches', 'acl.allow', 'acl.deny'])
205 213
206 214 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
207 215 denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
208 216 allow = buildmatch(ui, repo, user, 'acl.allow')
209 217 deny = buildmatch(ui, repo, user, 'acl.deny')
210 218
211 219 for rev in xrange(repo[node], len(repo)):
212 220 ctx = repo[rev]
213 221 branch = ctx.branch()
214 222 if denybranches and denybranches(branch):
215 223 raise util.Abort(_('acl: user "%s" denied on branch "%s"'
216 224 ' (changeset "%s")')
217 225 % (user, branch, ctx))
218 226 if allowbranches and not allowbranches(branch):
219 227 raise util.Abort(_('acl: user "%s" not allowed on branch "%s"'
220 228 ' (changeset "%s")')
221 229 % (user, branch, ctx))
222 230 ui.debug('acl: branch access granted: "%s" on branch "%s"\n'
223 231 % (ctx, branch))
224 232
225 233 for f in ctx.files():
226 234 if deny and deny(f):
227 235 ui.debug('acl: user %s denied on %s\n' % (user, f))
228 236 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
229 237 if allow and not allow(f):
230 238 ui.debug('acl: user %s not allowed on %s\n' % (user, f))
231 239 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
232 240 ui.debug('acl: allowing changeset %s\n' % ctx)
@@ -1,162 +1,168
1 1 #!/bin/sh
2 2
3 3 do_push()
4 4 {
5 5 user=$1
6 6 shift
7 7
8 8 echo "Pushing as user $user"
9 9 echo 'hgrc = """'
10 10 sed -e 1,2d b/.hg/hgrc | grep -v "$HGTMP"
11 11 echo '"""'
12 12 if test -f acl.config; then
13 13 echo 'acl.config = """'
14 14 cat acl.config
15 15 echo '"""'
16 16 fi
17 17 # On AIX /etc/profile sets LOGNAME read-only. So
18 18 # LOGNAME=$user hg --cws a --debug push ../b
19 19 # fails with "This variable is read only."
20 20 # Use env to work around this.
21 21 env LOGNAME=$user hg --cwd a --debug push ../b
22 22 hg --cwd b rollback
23 23 hg --cwd b --quiet tip
24 24 echo
25 25 }
26 26
27 27 init_config()
28 28 {
29 29 cat > fakegroups.py <<EOF
30 30 from hgext import acl
31 acl._getusers = lambda x: ["fred", "betty"]
31 def fakegetusers(ui, group):
32 try:
33 return acl._getusersorig(ui, group)
34 except:
35 return ["fred", "betty"]
36 acl._getusersorig = acl._getusers
37 acl._getusers = fakegetusers
32 38 EOF
33 39
34 40 rm -f acl.config
35 41 cat > $config <<EOF
36 42 [hooks]
37 43 pretxnchangegroup.acl = python:hgext.acl.hook
38 44 [acl]
39 45 sources = push
40 46 [extensions]
41 47 f=$PWD/fakegroups.py
42 48 EOF
43 49 }
44 50
45 51 hg init a
46 52 cd a
47 53 mkdir foo foo/Bar quux
48 54 echo 'in foo' > foo/file.txt
49 55 echo 'in foo/Bar' > foo/Bar/file.txt
50 56 echo 'in quux' > quux/file.py
51 57 hg add -q
52 58 hg ci -m 'add files' -d '1000000 0'
53 59 echo >> foo/file.txt
54 60 hg ci -m 'change foo/file' -d '1000001 0'
55 61 echo >> foo/Bar/file.txt
56 62 hg ci -m 'change foo/Bar/file' -d '1000002 0'
57 63 echo >> quux/file.py
58 64 hg ci -m 'change quux/file' -d '1000003 0'
59 65 hg tip --quiet
60 66
61 67 cd ..
62 68 hg clone -r 0 a b
63 69
64 70 echo '[extensions]' >> $HGRCPATH
65 71 echo 'acl =' >> $HGRCPATH
66 72
67 73 config=b/.hg/hgrc
68 74
69 75 echo
70 76
71 77 echo 'Extension disabled for lack of a hook'
72 78 do_push fred
73 79
74 80 echo '[hooks]' >> $config
75 81 echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
76 82
77 83 echo 'Extension disabled for lack of acl.sources'
78 84 do_push fred
79 85
80 86 echo 'No [acl.allow]/[acl.deny]'
81 87 echo '[acl]' >> $config
82 88 echo 'sources = push' >> $config
83 89 do_push fred
84 90
85 91 echo 'Empty [acl.allow]'
86 92 echo '[acl.allow]' >> $config
87 93 do_push fred
88 94
89 95 echo 'fred is allowed inside foo/'
90 96 echo 'foo/** = fred' >> $config
91 97 do_push fred
92 98
93 99 echo 'Empty [acl.deny]'
94 100 echo '[acl.deny]' >> $config
95 101 do_push barney
96 102
97 103 echo 'fred is allowed inside foo/, but not foo/bar/ (case matters)'
98 104 echo 'foo/bar/** = fred' >> $config
99 105 do_push fred
100 106
101 107 echo 'fred is allowed inside foo/, but not foo/Bar/'
102 108 echo 'foo/Bar/** = fred' >> $config
103 109 do_push fred
104 110
105 111 echo 'barney is not mentioned => not allowed anywhere'
106 112 do_push barney
107 113
108 114 echo 'barney is allowed everywhere'
109 115 echo '[acl.allow]' >> $config
110 116 echo '** = barney' >> $config
111 117 do_push barney
112 118
113 119 echo 'wilma can change files with a .txt extension'
114 120 echo '**/*.txt = wilma' >> $config
115 121 do_push wilma
116 122
117 123 echo 'file specified by acl.config does not exist'
118 124 echo '[acl]' >> $config
119 125 echo 'config = ../acl.config' >> $config
120 126 do_push barney
121 127
122 128 echo 'betty is allowed inside foo/ by a acl.config file'
123 129 echo '[acl.allow]' >> acl.config
124 130 echo 'foo/** = betty' >> acl.config
125 131 do_push betty
126 132
127 133 echo 'acl.config can set only [acl.allow]/[acl.deny]'
128 134 echo '[hooks]' >> acl.config
129 135 echo 'changegroup.acl = false' >> acl.config
130 136 do_push barney
131 137
132 138 # asterisk
133 139
134 140 init_config
135 141
136 142 echo 'asterisk test'
137 143 echo '[acl.allow]' >> $config
138 144 echo "** = fred" >> $config
139 145 echo "fred is always allowed"
140 146 do_push fred
141 147
142 148 echo '[acl.deny]' >> $config
143 149 echo "foo/Bar/** = *" >> $config
144 150 echo "no one is allowed inside foo/Bar/"
145 151 do_push fred
146 152
147 153 # Groups
148 154
149 155 init_config
150 156
151 157 echo 'OS-level groups'
152 158 echo '[acl.allow]' >> $config
153 159 echo "** = @group1" >> $config
154 160 echo "@group1 is always allowed"
155 161 do_push fred
156 162
157 163 echo '[acl.deny]' >> $config
158 164 echo "foo/Bar/** = @group1" >> $config
159 165 echo "@group is allowed inside anything but foo/Bar/"
160 166 do_push fred
161 167
162 168
General Comments 0
You need to be logged in to leave comments. Login now