##// END OF EJS Templates
ui: Rename has_config to has_section.
Bryan O'Sullivan -
r4487:1b5b9883 default
parent child Browse files
Show More
@@ -1,124 +1,124 b''
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # this hook allows to allow or deny access to parts of a repo when
9 9 # taking incoming changesets.
10 10 #
11 11 # authorization is against local user name on system where hook is
12 12 # run, not committer of original changeset (since that is easy to
13 13 # spoof).
14 14 #
15 15 # acl hook is best to use if you use hgsh to set up restricted shells
16 16 # for authenticated users to only push to / pull from. not safe if
17 17 # user has interactive shell access, because they can disable hook.
18 18 # also not safe if remote users share one local account, because then
19 19 # no way to tell remote users apart.
20 20 #
21 21 # to use, configure acl extension in hgrc like this:
22 22 #
23 23 # [extensions]
24 24 # hgext.acl =
25 25 #
26 26 # [hooks]
27 27 # pretxnchangegroup.acl = python:hgext.acl.hook
28 28 #
29 29 # [acl]
30 30 # sources = serve # check if source of incoming changes in this list
31 31 # # ("serve" == ssh or http, "push", "pull", "bundle")
32 32 #
33 33 # allow and deny lists have subtree pattern (default syntax is glob)
34 34 # on left, user names on right. deny list checked before allow list.
35 35 #
36 36 # [acl.allow]
37 37 # # if acl.allow not present, all users allowed by default
38 38 # # empty acl.allow = no users allowed
39 39 # docs/** = doc_writer
40 40 # .hgtags = release_engineer
41 41 #
42 42 # [acl.deny]
43 43 # # if acl.deny not present, no users denied by default
44 44 # # empty acl.deny = all users allowed
45 45 # glob pattern = user4, user5
46 46 # ** = user6
47 47
48 48 from mercurial.i18n import _
49 49 from mercurial.node import *
50 50 from mercurial import util
51 51 import getpass
52 52
53 53 class checker(object):
54 54 '''acl checker.'''
55 55
56 56 def buildmatch(self, key):
57 57 '''return tuple of (match function, list enabled).'''
58 if not self.ui.has_config(key):
58 if not self.ui.has_section(key):
59 59 self.ui.debug(_('acl: %s not enabled\n') % key)
60 60 return None, False
61 61
62 62 thisuser = self.getuser()
63 63 pats = [pat for pat, users in self.ui.configitems(key)
64 64 if thisuser in users.replace(',', ' ').split()]
65 65 self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
66 66 (key, len(pats), thisuser))
67 67 if pats:
68 68 match = util.matcher(self.repo.root, names=pats)[1]
69 69 else:
70 70 match = util.never
71 71 return match, True
72 72
73 73 def getuser(self):
74 74 '''return name of authenticated user.'''
75 75 return self.user
76 76
77 77 def __init__(self, ui, repo):
78 78 self.ui = ui
79 79 self.repo = repo
80 80 self.user = getpass.getuser()
81 81 cfg = self.ui.config('acl', 'config')
82 82 if cfg:
83 83 self.ui.readsections(cfg, 'acl.allow', 'acl.deny')
84 84 self.allow, self.allowable = self.buildmatch('acl.allow')
85 85 self.deny, self.deniable = self.buildmatch('acl.deny')
86 86
87 87 def skipsource(self, source):
88 88 '''true if incoming changes from this source should be skipped.'''
89 89 ok_sources = self.ui.config('acl', 'sources', 'serve').split()
90 90 return source not in ok_sources
91 91
92 92 def check(self, node):
93 93 '''return if access allowed, raise exception if not.'''
94 94 files = self.repo.changectx(node).files()
95 95 if self.deniable:
96 96 for f in files:
97 97 if self.deny(f):
98 98 self.ui.debug(_('acl: user %s denied on %s\n') %
99 99 (self.getuser(), f))
100 100 raise util.Abort(_('acl: access denied for changeset %s') %
101 101 short(node))
102 102 if self.allowable:
103 103 for f in files:
104 104 if not self.allow(f):
105 105 self.ui.debug(_('acl: user %s not allowed on %s\n') %
106 106 (self.getuser(), f))
107 107 raise util.Abort(_('acl: access denied for changeset %s') %
108 108 short(node))
109 109 self.ui.debug(_('acl: allowing changeset %s\n') % short(node))
110 110
111 111 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
112 112 if hooktype != 'pretxnchangegroup':
113 113 raise util.Abort(_('config error - hook type "%s" cannot stop '
114 114 'incoming changesets') % hooktype)
115 115
116 116 c = checker(ui, repo)
117 117 if c.skipsource(source):
118 118 ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
119 119 return
120 120
121 121 start = repo.changelog.rev(bin(node))
122 122 end = repo.changelog.count()
123 123 for rev in xrange(start, end):
124 124 c.check(repo.changelog.node(rev))
@@ -1,450 +1,450 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, re, socket, sys, tempfile
10 10 import ConfigParser, traceback, util
11 11
12 12 def dupconfig(orig):
13 13 new = util.configparser(orig.defaults())
14 14 updateconfig(orig, new)
15 15 return new
16 16
17 17 def updateconfig(source, dest, sections=None):
18 18 if not sections:
19 19 sections = source.sections()
20 20 for section in sections:
21 21 if not dest.has_section(section):
22 22 dest.add_section(section)
23 23 for name, value in source.items(section, raw=True):
24 24 dest.set(section, name, value)
25 25
26 26 class ui(object):
27 27 def __init__(self, verbose=False, debug=False, quiet=False,
28 28 interactive=True, traceback=False, report_untrusted=True,
29 29 parentui=None):
30 30 self.overlay = None
31 31 self.buffers = []
32 32 if parentui is None:
33 33 # this is the parent of all ui children
34 34 self.parentui = None
35 35 self.readhooks = []
36 36 self.quiet = quiet
37 37 self.verbose = verbose
38 38 self.debugflag = debug
39 39 self.interactive = interactive
40 40 self.traceback = traceback
41 41 self.report_untrusted = report_untrusted
42 42 self.trusted_users = {}
43 43 self.trusted_groups = {}
44 44 # if ucdata is not None, its keys must be a superset of cdata's
45 45 self.cdata = util.configparser()
46 46 self.ucdata = None
47 47 # we always trust global config files
48 48 self.check_trusted = False
49 49 self.readconfig(util.rcpath())
50 50 self.check_trusted = True
51 51 self.updateopts(verbose, debug, quiet, interactive)
52 52 else:
53 53 # parentui may point to an ui object which is already a child
54 54 self.parentui = parentui.parentui or parentui
55 55 self.readhooks = self.parentui.readhooks[:]
56 56 self.trusted_users = parentui.trusted_users.copy()
57 57 self.trusted_groups = parentui.trusted_groups.copy()
58 58 self.cdata = dupconfig(self.parentui.cdata)
59 59 if self.parentui.ucdata:
60 60 self.ucdata = dupconfig(self.parentui.ucdata)
61 61 if self.parentui.overlay:
62 62 self.overlay = dupconfig(self.parentui.overlay)
63 63
64 64 def __getattr__(self, key):
65 65 return getattr(self.parentui, key)
66 66
67 67 def updateopts(self, verbose=False, debug=False, quiet=False,
68 68 interactive=True, traceback=False, config=[]):
69 69 for section, name, value in config:
70 70 self.setconfig(section, name, value)
71 71
72 72 if quiet or verbose or debug:
73 73 self.setconfig('ui', 'quiet', str(bool(quiet)))
74 74 self.setconfig('ui', 'verbose', str(bool(verbose)))
75 75 self.setconfig('ui', 'debug', str(bool(debug)))
76 76
77 77 self.verbosity_constraints()
78 78
79 79 if not interactive:
80 80 self.setconfig('ui', 'interactive', 'False')
81 81 self.interactive = False
82 82
83 83 self.traceback = self.traceback or traceback
84 84
85 85 def verbosity_constraints(self):
86 86 self.quiet = self.configbool('ui', 'quiet')
87 87 self.verbose = self.configbool('ui', 'verbose')
88 88 self.debugflag = self.configbool('ui', 'debug')
89 89
90 90 if self.debugflag:
91 91 self.verbose = True
92 92 self.quiet = False
93 93 elif self.verbose and self.quiet:
94 94 self.quiet = self.verbose = False
95 95
96 96 def _is_trusted(self, fp, f, warn=True):
97 97 if not self.check_trusted:
98 98 return True
99 99 st = util.fstat(fp)
100 100 if util.isowner(fp, st):
101 101 return True
102 102 tusers = self.trusted_users
103 103 tgroups = self.trusted_groups
104 104 if not tusers:
105 105 user = util.username()
106 106 if user is not None:
107 107 self.trusted_users[user] = 1
108 108 self.fixconfig(section='trusted')
109 109 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
110 110 user = util.username(st.st_uid)
111 111 group = util.groupname(st.st_gid)
112 112 if user not in tusers and group not in tgroups:
113 113 if warn and self.report_untrusted:
114 114 self.warn(_('Not trusting file %s from untrusted '
115 115 'user %s, group %s\n') % (f, user, group))
116 116 return False
117 117 return True
118 118
119 119 def readconfig(self, fn, root=None):
120 120 if isinstance(fn, basestring):
121 121 fn = [fn]
122 122 for f in fn:
123 123 try:
124 124 fp = open(f)
125 125 except IOError:
126 126 continue
127 127 cdata = self.cdata
128 128 trusted = self._is_trusted(fp, f)
129 129 if not trusted:
130 130 if self.ucdata is None:
131 131 self.ucdata = dupconfig(self.cdata)
132 132 cdata = self.ucdata
133 133 elif self.ucdata is not None:
134 134 # use a separate configparser, so that we don't accidentally
135 135 # override ucdata settings later on.
136 136 cdata = util.configparser()
137 137
138 138 try:
139 139 cdata.readfp(fp, f)
140 140 except ConfigParser.ParsingError, inst:
141 141 msg = _("Failed to parse %s\n%s") % (f, inst)
142 142 if trusted:
143 143 raise util.Abort(msg)
144 144 self.warn(_("Ignored: %s\n") % msg)
145 145
146 146 if trusted:
147 147 if cdata != self.cdata:
148 148 updateconfig(cdata, self.cdata)
149 149 if self.ucdata is not None:
150 150 updateconfig(cdata, self.ucdata)
151 151 # override data from config files with data set with ui.setconfig
152 152 if self.overlay:
153 153 updateconfig(self.overlay, self.cdata)
154 154 if root is None:
155 155 root = os.path.expanduser('~')
156 156 self.fixconfig(root=root)
157 157 for hook in self.readhooks:
158 158 hook(self)
159 159
160 160 def addreadhook(self, hook):
161 161 self.readhooks.append(hook)
162 162
163 163 def readsections(self, filename, *sections):
164 164 """Read filename and add only the specified sections to the config data
165 165
166 166 The settings are added to the trusted config data.
167 167 """
168 168 if not sections:
169 169 return
170 170
171 171 cdata = util.configparser()
172 172 try:
173 173 cdata.read(filename)
174 174 except ConfigParser.ParsingError, inst:
175 175 raise util.Abort(_("failed to parse %s\n%s") % (filename,
176 176 inst))
177 177
178 178 for section in sections:
179 179 if not cdata.has_section(section):
180 180 cdata.add_section(section)
181 181
182 182 updateconfig(cdata, self.cdata, sections)
183 183 if self.ucdata:
184 184 updateconfig(cdata, self.ucdata, sections)
185 185
186 186 def fixconfig(self, section=None, name=None, value=None, root=None):
187 187 # translate paths relative to root (or home) into absolute paths
188 188 if section is None or section == 'paths':
189 189 if root is None:
190 190 root = os.getcwd()
191 191 items = section and [(name, value)] or []
192 192 for cdata in self.cdata, self.ucdata, self.overlay:
193 193 if not cdata: continue
194 194 if not items and cdata.has_section('paths'):
195 195 pathsitems = cdata.items('paths')
196 196 else:
197 197 pathsitems = items
198 198 for n, path in pathsitems:
199 199 if path and "://" not in path and not os.path.isabs(path):
200 200 cdata.set("paths", n, os.path.join(root, path))
201 201
202 202 # update quiet/verbose/debug and interactive status
203 203 if section is None or section == 'ui':
204 204 if name is None or name in ('quiet', 'verbose', 'debug'):
205 205 self.verbosity_constraints()
206 206
207 207 if name is None or name == 'interactive':
208 208 self.interactive = self.configbool("ui", "interactive", True)
209 209
210 210 # update trust information
211 211 if (section is None or section == 'trusted') and self.trusted_users:
212 212 for user in self.configlist('trusted', 'users'):
213 213 self.trusted_users[user] = 1
214 214 for group in self.configlist('trusted', 'groups'):
215 215 self.trusted_groups[group] = 1
216 216
217 217 def setconfig(self, section, name, value):
218 218 if not self.overlay:
219 219 self.overlay = util.configparser()
220 220 for cdata in (self.overlay, self.cdata, self.ucdata):
221 221 if not cdata: continue
222 222 if not cdata.has_section(section):
223 223 cdata.add_section(section)
224 224 cdata.set(section, name, value)
225 225 self.fixconfig(section, name, value)
226 226
227 227 def _get_cdata(self, untrusted):
228 228 if untrusted and self.ucdata:
229 229 return self.ucdata
230 230 return self.cdata
231 231
232 232 def _config(self, section, name, default, funcname, untrusted, abort):
233 233 cdata = self._get_cdata(untrusted)
234 234 if cdata.has_option(section, name):
235 235 try:
236 236 func = getattr(cdata, funcname)
237 237 return func(section, name)
238 238 except ConfigParser.InterpolationError, inst:
239 239 msg = _("Error in configuration section [%s] "
240 240 "parameter '%s':\n%s") % (section, name, inst)
241 241 if abort:
242 242 raise util.Abort(msg)
243 243 self.warn(_("Ignored: %s\n") % msg)
244 244 return default
245 245
246 246 def _configcommon(self, section, name, default, funcname, untrusted):
247 247 value = self._config(section, name, default, funcname,
248 248 untrusted, abort=True)
249 249 if self.debugflag and not untrusted and self.ucdata:
250 250 uvalue = self._config(section, name, None, funcname,
251 251 untrusted=True, abort=False)
252 252 if uvalue is not None and uvalue != value:
253 253 self.warn(_("Ignoring untrusted configuration option "
254 254 "%s.%s = %s\n") % (section, name, uvalue))
255 255 return value
256 256
257 257 def config(self, section, name, default=None, untrusted=False):
258 258 return self._configcommon(section, name, default, 'get', untrusted)
259 259
260 260 def configbool(self, section, name, default=False, untrusted=False):
261 261 return self._configcommon(section, name, default, 'getboolean',
262 262 untrusted)
263 263
264 264 def configlist(self, section, name, default=None, untrusted=False):
265 265 """Return a list of comma/space separated strings"""
266 266 result = self.config(section, name, untrusted=untrusted)
267 267 if result is None:
268 268 result = default or []
269 269 if isinstance(result, basestring):
270 270 result = result.replace(",", " ").split()
271 271 return result
272 272
273 def has_config(self, section, untrusted=False):
273 def has_section(self, section, untrusted=False):
274 274 '''tell whether section exists in config.'''
275 275 cdata = self._get_cdata(untrusted)
276 276 return cdata.has_section(section)
277 277
278 278 def _configitems(self, section, untrusted, abort):
279 279 items = {}
280 280 cdata = self._get_cdata(untrusted)
281 281 if cdata.has_section(section):
282 282 try:
283 283 items.update(dict(cdata.items(section)))
284 284 except ConfigParser.InterpolationError, inst:
285 285 msg = _("Error in configuration section [%s]:\n"
286 286 "%s") % (section, inst)
287 287 if abort:
288 288 raise util.Abort(msg)
289 289 self.warn(_("Ignored: %s\n") % msg)
290 290 return items
291 291
292 292 def configitems(self, section, untrusted=False):
293 293 items = self._configitems(section, untrusted=untrusted, abort=True)
294 294 if self.debugflag and not untrusted and self.ucdata:
295 295 uitems = self._configitems(section, untrusted=True, abort=False)
296 296 keys = uitems.keys()
297 297 keys.sort()
298 298 for k in keys:
299 299 if uitems[k] != items.get(k):
300 300 self.warn(_("Ignoring untrusted configuration option "
301 301 "%s.%s = %s\n") % (section, k, uitems[k]))
302 302 x = items.items()
303 303 x.sort()
304 304 return x
305 305
306 306 def walkconfig(self, untrusted=False):
307 307 cdata = self._get_cdata(untrusted)
308 308 sections = cdata.sections()
309 309 sections.sort()
310 310 for section in sections:
311 311 for name, value in self.configitems(section, untrusted):
312 312 yield section, name, str(value).replace('\n', '\\n')
313 313
314 314 def extensions(self):
315 315 result = self.configitems("extensions")
316 316 for i, (key, value) in enumerate(result):
317 317 if value:
318 318 result[i] = (key, os.path.expanduser(value))
319 319 return result
320 320
321 321 def hgignorefiles(self):
322 322 result = []
323 323 for key, value in self.configitems("ui"):
324 324 if key == 'ignore' or key.startswith('ignore.'):
325 325 result.append(os.path.expanduser(value))
326 326 return result
327 327
328 328 def username(self):
329 329 """Return default username to be used in commits.
330 330
331 331 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
332 332 and stop searching if one of these is set.
333 333 If not found, use ($LOGNAME or $USER or $LNAME or
334 334 $USERNAME) +"@full.hostname".
335 335 """
336 336 user = os.environ.get("HGUSER")
337 337 if user is None:
338 338 user = self.config("ui", "username")
339 339 if user is None:
340 340 user = os.environ.get("EMAIL")
341 341 if user is None:
342 342 try:
343 343 user = '%s@%s' % (util.getuser(), socket.getfqdn())
344 344 self.warn(_("No username found, using '%s' instead\n") % user)
345 345 except KeyError:
346 346 pass
347 347 if not user:
348 348 raise util.Abort(_("Please specify a username."))
349 349 return user
350 350
351 351 def shortuser(self, user):
352 352 """Return a short representation of a user name or email address."""
353 353 if not self.verbose: user = util.shortuser(user)
354 354 return user
355 355
356 356 def expandpath(self, loc, default=None):
357 357 """Return repository location relative to cwd or from [paths]"""
358 358 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
359 359 return loc
360 360
361 361 path = self.config("paths", loc)
362 362 if not path and default is not None:
363 363 path = self.config("paths", default)
364 364 return path or loc
365 365
366 366 def pushbuffer(self):
367 367 self.buffers.append([])
368 368
369 369 def popbuffer(self):
370 370 return "".join(self.buffers.pop())
371 371
372 372 def write(self, *args):
373 373 if self.buffers:
374 374 self.buffers[-1].extend([str(a) for a in args])
375 375 else:
376 376 for a in args:
377 377 sys.stdout.write(str(a))
378 378
379 379 def write_err(self, *args):
380 380 try:
381 381 if not sys.stdout.closed: sys.stdout.flush()
382 382 for a in args:
383 383 sys.stderr.write(str(a))
384 384 # stderr may be buffered under win32 when redirected to files,
385 385 # including stdout.
386 386 if not sys.stderr.closed: sys.stderr.flush()
387 387 except IOError, inst:
388 388 if inst.errno != errno.EPIPE:
389 389 raise
390 390
391 391 def flush(self):
392 392 try: sys.stdout.flush()
393 393 except: pass
394 394 try: sys.stderr.flush()
395 395 except: pass
396 396
397 397 def readline(self):
398 398 return sys.stdin.readline()[:-1]
399 399 def prompt(self, msg, pat=None, default="y"):
400 400 if not self.interactive: return default
401 401 while 1:
402 402 self.write(msg, " ")
403 403 r = self.readline()
404 404 if not pat or re.match(pat, r):
405 405 return r
406 406 else:
407 407 self.write(_("unrecognized response\n"))
408 408 def getpass(self, prompt=None, default=None):
409 409 if not self.interactive: return default
410 410 return getpass.getpass(prompt or _('password: '))
411 411 def status(self, *msg):
412 412 if not self.quiet: self.write(*msg)
413 413 def warn(self, *msg):
414 414 self.write_err(*msg)
415 415 def note(self, *msg):
416 416 if self.verbose: self.write(*msg)
417 417 def debug(self, *msg):
418 418 if self.debugflag: self.write(*msg)
419 419 def edit(self, text, user):
420 420 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
421 421 text=True)
422 422 try:
423 423 f = os.fdopen(fd, "w")
424 424 f.write(text)
425 425 f.close()
426 426
427 427 editor = (os.environ.get("HGEDITOR") or
428 428 self.config("ui", "editor") or
429 429 os.environ.get("EDITOR", "vi"))
430 430
431 431 util.system("%s \"%s\"" % (editor, name),
432 432 environ={'HGUSER': user},
433 433 onerr=util.Abort, errprefix=_("edit failed"))
434 434
435 435 f = open(name)
436 436 t = f.read()
437 437 f.close()
438 438 t = re.sub("(?m)^HG:.*\n", "", t)
439 439 finally:
440 440 os.unlink(name)
441 441
442 442 return t
443 443
444 444 def print_exc(self):
445 445 '''print exception traceback if traceback printing enabled.
446 446 only to call in exception handler. returns true if traceback
447 447 printed.'''
448 448 if self.traceback:
449 449 traceback.print_exc()
450 450 return self.traceback
General Comments 0
You need to be logged in to leave comments. Login now