# HG changeset patch # User Dirkjan Ochtman # Date 2008-06-29 09:35:06 # Node ID d3147b4e3e8a4b0278f3905bce3629768994d550 # Parent 959efdac4a9c7aa0e0018b3652e77bd894401a66 hgweb: centralize permission checks for protocol commands Consistently enforces authorization checks set up in hgrc up front, so that the actual commands don't have to worry about them and implementers of hgweb alternatives can easily implement their own permission checks. diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -16,6 +16,13 @@ from common import HTTP_OK, HTTP_BAD_REQ from request import wsgirequest import webcommands, protocol, webutil +perms = { + 'changegroup': 'pull', + 'changegroupsubset': 'pull', + 'unbundle': 'push', + 'stream_out': 'pull', +} + class hgweb(object): def __init__(self, repo, name=None): if isinstance(repo, str): @@ -95,6 +102,8 @@ class hgweb(object): cmd = req.form.get('cmd', [''])[0] if cmd and cmd in protocol.__all__: + if cmd in perms and not self.check_perm(req, perms[cmd]): + return method = getattr(protocol, cmd) method(self, req) return @@ -343,16 +352,39 @@ class hgweb(object): 'zip': ('application/zip', 'zip', '.zip', None), } - def check_perm(self, req, op, default): - '''check permission for operation based on user auth. - return true if op allowed, else false. - default is policy to use if no config given.''' + def check_perm(self, req, op): + '''Check permission for operation based on request data (including + authentication info. Return true if op allowed, else false.''' + + def error(status, message): + req.respond(status, protocol.HGTYPE) + req.write('0\n%s\n' % message) + + if op == 'pull': + return self.allowpull + + # enforce that you can only push using POST requests + if req.env['REQUEST_METHOD'] != 'POST': + error('405 Method Not Allowed', 'push requires POST request') + return False + + # require ssl by default for pushing, auth info cannot be sniffed + # and replayed + scheme = req.env.get('wsgi.url_scheme') + if self.configbool('web', 'push_ssl', True) and scheme != 'https': + error(HTTP_OK, 'ssl required') + return False user = req.env.get('REMOTE_USER') - deny = self.configlist('web', 'deny_' + op) + deny = self.configlist('web', 'deny_push') if deny and (not user or deny == ['*'] or user in deny): + error('401 Unauthorized', 'push not authorized') return False - allow = self.configlist('web', 'allow_' + op) - return (allow and (allow == ['*'] or user in allow)) or default + allow = self.configlist('web', 'allow_push') + result = allow and (allow == ['*'] or user in allow) + if not result: + error('401 Unauthorized', 'push not authorized') + + return result diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py +++ b/mercurial/hgweb/protocol.py @@ -62,8 +62,6 @@ def between(web, req): def changegroup(web, req): req.respond(HTTP_OK, HGTYPE) nodes = [] - if not web.allowpull: - return if 'roots' in req.form: nodes = map(bin, req.form['roots'][0].split(" ")) @@ -82,8 +80,6 @@ def changegroupsubset(web, req): req.respond(HTTP_OK, HGTYPE) bases = [] heads = [] - if not web.allowpull: - return if 'bases' in req.form: bases = [bin(x) for x in req.form['bases'][0].split(' ')] @@ -120,28 +116,7 @@ def unbundle(web, req): req.write('0\n') req.write(response) - # enforce that you can only unbundle with POST requests - if req.env['REQUEST_METHOD'] != 'POST': - headers = {'status': '405 Method Not Allowed'} - bail('unbundle requires POST request\n', headers) - return - - # require ssl by default, auth info cannot be sniffed and - # replayed - ssl_req = web.configbool('web', 'push_ssl', True) - if ssl_req: - if req.env.get('wsgi.url_scheme') != 'https': - bail('ssl required\n') - return - proto = 'https' - else: - proto = 'http' - - # do not allow push unless explicitly allowed - if not web.check_perm(req, 'push', False): - bail('push not authorized\n', headers={'status': '401 Unauthorized'}) - return - + proto = req.env.get('wsgi.url_scheme') or 'http' their_heads = req.form['heads'][0].split(' ') def check_heads(): @@ -224,7 +199,5 @@ def unbundle(web, req): os.unlink(tempname) def stream_out(web, req): - if not web.allowpull: - return req.respond(HTTP_OK, HGTYPE) streamclone.stream_out(web.repo, req, untrusted=True) diff --git a/tests/test-hgweb-commands.out b/tests/test-hgweb-commands.out index a9666456a4e9c97af4b479b399d886234b61ab19..fdbe42f38f282b6fbc29f5d7e0478c864520810e GIT binary patch literal 18057 zc%1E9>2l*X5}y4zs(pwKUCw1oBxU)MWo0I|cap8lQDaZ-@n;qyA&E5<$t5An&haMu zu-gp~yhNR`r;;iwjzj{DZlJr-{o$BhxaI<;5r{E|m`7oQxvm$Cz-8tz%--m67k&yy z39!HgXUu{TPa18@f@>%CA_3u4L{ou;z8}t#Mx*!L-Nc7m9w%NHoEvt_GJpq8NbJrH zmN=egeAjREhMc>Aq(O3SjD?80rfIj2TNeIjo5=srd?~_-(QiQSi)Irpq+~xFL9m)ibda>wGWxgk$p0jfDF{1I95Q)(Bz9zlj_Tv)qXJg2w?9d|^w~LU_XGeZdov z_pWEEQmmdvF7j~u_UWpO2{r;$A*wgl z%15)c`Osp{ap#oTom^tQQYJihrm@F-b0=faWS!w@o4f6U)06gqTZc!3)04xt%MaZl zKRG_&47m?2m!}#{wX;AQF~2p}WhgZVJvJ5NFs4W71#F_U*fZs$EaVgB`TZ|(u`+XB8>t-0qv^o+lJW8eIT1B+!a$&7E$oov~PjiknU(pvHN5kV`r*kq~+iBKi_~bMzoJN^+J%?!YObWgIrx^Ye!=GaK zQw)EK;eQOL`4hzOIxo`!7@aYqg~OKdTQ{#pP1F3x!G&pF-dw7RPRp|4CT2n637l!@F6Px6>}`*F zO+TEWpu&9cutR!2n2BJ*bIkFGN?TDdGExnB6E@-|6=35D7YxY8=SCb3LXjAdjtU6_ zm*4F}5UQRVV3wpM;@;6~vW>ozgG5%W9>pvg8-4jxsX`optmwN^G4y<10$rxjr!qxH zgo{U89cmvRH2c-9*0KN*DVAz3p)0rh+7U83)MK0K@O=Wz5i;4BvP)PRksaB9`$SKv zfW^)j)uAn^FQPgT`diaOEUn1D|WT2{yZ;2m=v|J#lZz ztSMB86|N@3iEZ>JEVzez93YxaxK#=nnT<5K&s7JZTN+yxyl;X)Sw9tkKQWC=j;Oz4 zWcFtqS=nGJ%rENoW{%k2%n{pvU`H&)FKiSRgIAhXXR}!g!59xbr{#o`0>B|+Z!&@; zc8WQP_^6yT^IS1LKR$#p_eNuJermxWj9ng|TY%B|+{k$0P)jkxRPhzVgiMD|8F$T$ zyJDkYoAhNZPG6a?Ax)m^A11?<{X9=_g;~yK&H0&*rD-B8hw`0O&6}YYO~BIBS2{eTzbPHO#oUlrMxm+(!=#rpi3uSFikLksXV2V z2)+lQ4q-dKtx_TW_CbAY8fCND1k*(~4W_>Te7V)IwG*d7h8|3ld|t*>}3Smqf=MSQ!Z)eZD?<0 z!&YX+mbN24eKtGVt#&5AoyjlQ^3e=ww+=GN2btstHKyI_WFk75hy@1ClG>jJxGjZl zjUD*mRROa^OgLjK{E%?9OvL9)jO$>5g^zC#=EIMdush`|ynRUnlFD$v2+E`^tH=f)juV^qOf@_ zV$Ts0SV1u|%??^&JThrZX-a~9C>2K2a9oCdBgTvP0Y`BLTaurVyh!kb2&hb&L6=a+s_1{GDo~Y^2YVSRYJC2^Jg$r3XApNn;N&1-hJa zAIZ^xTjo#kE#)kItT1kM6jWuTPJ{}lMzWa(3F?d{OUhL(2J9C3rQL>KYao4PutjuV zqMs)%Efz|&auHCW&K3qGO3F=Db*Jlu65Sl;t~4sF1qQKMuMp`RM*quTkVI!%f$32s z_kFtcA)}vV=>2ZD2pAQ@#Q&$kry!gKbGzeiA4_?h(S5mdY)Kq#XWs{H{5m3}eR?bl zJ6WM5wc3>i9Z7pqZgAAep?cfO>a0^qAz4@_8Hr+XnlqMkN9`)Or!J$SL<)VgVIT@7OoFcrib)h!FX{R-DhA-7t{tg_BL(ykm8_q@K|$HzR~&xV8LYC;#w z3Mf6vBX)JcRq1bUu)(@v(1+5l9}ZtFi%X@0m<7(5mMS`wxqQf`zM!Rxd`Nft%FM7q zu}yxco6_ptL6!}|CY3??2##Dmh z68;}E1Z?Xe(!ztqW_<{IRC<7S7Gn*L;tp6zP!&?c7QxE zuXc#7LTYtOb2Eq7EEd-|gx*P85*%_v7JZ<)O*V&3hhZob!mqFSsFt}Zz$>RH5aKH1 zV|6<$zp{vHj_O{oUib80RxTeRVpMzS8dFy`wsyf`5gT~EC%onVKGhGyPtz#H0HZjZ zM#*$QSYR3qrh$vP|KXKwz54e){d})}KD^llTI(!jy!K(%E~q{%V`jlupZNV(NUP`F zLx^7c_zbKyKWh_`g>scI8_eDRod3Ld_UzeTpZ#SQmxeKClaENVtci`>hYXF60Y#f} z9~Z{|oq91(;N9En8~H{Dksa$0UUM-HU3e1;cu6d{iyCi4(`3A$(JoxeodJ;Zaj7Zb z7oa)Obh#78a{b*6!hoN_Z;ckt8?2s{BV zoKK|az-?ks2;(m7xbjn=v+L=&{75>r9ZvFS1G)=GR&;j;nvluQ84f)VO}Y%(1$i}* z$7c=5K2jG9<5Zwm_7JImTi%qfU7Jr-r_v*K6qiaE#heGSF1y^2SH@h8MIHU;<Dbu7waJq3$JEs`iiG3@(j-2_X zZCSg@X?2yJZE5{srCN-&Uuew-VT`^}>ln8@#@Ww&T`a<=%8kVBL#brw#ff&$c^g;T z7paR~@c9sXkPgEO#vCWFR48icT3^R6DkoW@on(P}PEuFT>k&_^p&!+`Md~SXYpJt{ zqD7QU8pjPQ4S{y}uhQ3FfB;NW!XoMeh0=m!WzH|1tk|%XKHqP~B6mIl1#-JycBy=^ z6nXwOSGL%t>gQJ;ONYZ@t>4wjXvrM34>dJ~oiOal0;|vfX-w^r>Aq+;6SJiWH1|}XNDd|P!K9oAA zB^;B4-x@${<@PG^PS7Fup&-cjNY*&j>%l4J*|aN#HY-PF7X_aj%CT+G|2cu!E5sc` z3fj+IFNu71PZ=bn4D!5?QjamXEU=gAjjhJB3s>*ozkPoVS8p!fet2_p_5SLz@qheR B&RGBe