##// END OF EJS Templates
let action always return pull command for better security on pull restricted repos
marcink -
r1128:62a1d415 beta
parent child Browse files
Show More
@@ -1,272 +1,272
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software; you can redistribute it and/or
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program; if not, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29 import os
30 30 import logging
31 31 import traceback
32 32
33 33 from mercurial.error import RepoError
34 34 from mercurial.hgweb import hgweb
35 35 from mercurial.hgweb.request import wsgiapplication
36 36
37 37 from paste.auth.basic import AuthBasicAuthenticator
38 38 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
39 39
40 40 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
41 41 from rhodecode.lib.utils import make_ui, invalidate_cache, \
42 42 check_repo_fast, ui_sections
43 43 from rhodecode.model.user import UserModel
44 44
45 45 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 def is_mercurial(environ):
50 50 """Returns True if request's target is mercurial server - header
51 51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 52 """
53 53 http_accept = environ.get('HTTP_ACCEPT')
54 54 if http_accept and http_accept.startswith('application/mercurial'):
55 55 return True
56 56 return False
57 57
58 58 class SimpleHg(object):
59 59
60 60 def __init__(self, application, config):
61 61 self.application = application
62 62 self.config = config
63 63 #authenticate this mercurial request using authfunc
64 64 self.authenticate = AuthBasicAuthenticator('', authfunc)
65 65 self.ipaddr = '0.0.0.0'
66 66 self.repo_name = None
67 67 self.username = None
68 68 self.action = None
69 69
70 70 def __call__(self, environ, start_response):
71 71 if not is_mercurial(environ):
72 72 return self.application(environ, start_response)
73 73
74 74 proxy_key = 'HTTP_X_REAL_IP'
75 75 def_key = 'REMOTE_ADDR'
76 76 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
77 77 # skip passing error to error controller
78 78 environ['pylons.status_code_redirect'] = True
79 79
80 80 #======================================================================
81 81 # GET ACTION PULL or PUSH
82 82 #======================================================================
83 83 self.action = self.__get_action(environ)
84 84 try:
85 85 #==================================================================
86 86 # GET REPOSITORY NAME
87 87 #==================================================================
88 88 self.repo_name = self.__get_repository(environ)
89 89 except:
90 90 return HTTPInternalServerError()(environ, start_response)
91 91
92 92 #======================================================================
93 93 # CHECK ANONYMOUS PERMISSION
94 94 #======================================================================
95 95 if self.action in ['pull', 'push']:
96 96 anonymous_user = self.__get_user('default')
97 97 self.username = anonymous_user.username
98 98 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
99 99 self.repo_name)
100 100
101 101 if anonymous_perm is not True or anonymous_user.active is False:
102 102 if anonymous_perm is not True:
103 103 log.debug('Not enough credentials to access this repository'
104 104 'as anonymous user')
105 105 if anonymous_user.active is False:
106 106 log.debug('Anonymous access is disabled, running '
107 107 'authentication')
108 108 #==============================================================
109 109 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
110 110 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
111 111 #==============================================================
112 112
113 113 if not REMOTE_USER(environ):
114 114 self.authenticate.realm = str(self.config['rhodecode_realm'])
115 115 result = self.authenticate(environ)
116 116 if isinstance(result, str):
117 117 AUTH_TYPE.update(environ, 'basic')
118 118 REMOTE_USER.update(environ, result)
119 119 else:
120 120 return result.wsgi_application(environ, start_response)
121 121
122 122
123 123 #==============================================================
124 124 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
125 125 # BASIC AUTH
126 126 #==============================================================
127 127
128 128 if self.action in ['pull', 'push']:
129 129 username = REMOTE_USER(environ)
130 130 try:
131 131 user = self.__get_user(username)
132 132 self.username = user.username
133 133 except:
134 134 log.error(traceback.format_exc())
135 135 return HTTPInternalServerError()(environ, start_response)
136 136
137 137 #check permissions for this repository
138 138 perm = self.__check_permission(self.action, user, self.repo_name)
139 139 if perm is not True:
140 140 return HTTPForbidden()(environ, start_response)
141 141
142 142 self.extras = {'ip':self.ipaddr,
143 143 'username':self.username,
144 144 'action':self.action,
145 145 'repository':self.repo_name}
146 146
147 147 #===================================================================
148 148 # MERCURIAL REQUEST HANDLING
149 149 #===================================================================
150 150 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
151 151 self.baseui = make_ui('db')
152 152 self.basepath = self.config['base_path']
153 153 self.repo_path = os.path.join(self.basepath, self.repo_name)
154 154
155 155 #quick check if that dir exists...
156 156 if check_repo_fast(self.repo_name, self.basepath):
157 157 return HTTPNotFound()(environ, start_response)
158 158 try:
159 159 app = wsgiapplication(self.__make_app)
160 160 except RepoError, e:
161 161 if str(e).find('not found') != -1:
162 162 return HTTPNotFound()(environ, start_response)
163 163 except Exception:
164 164 log.error(traceback.format_exc())
165 165 return HTTPInternalServerError()(environ, start_response)
166 166
167 167 #invalidate cache on push
168 168 if self.action == 'push':
169 169 self.__invalidate_cache(self.repo_name)
170 170
171 171 return app(environ, start_response)
172 172
173 173
174 174 def __make_app(self):
175 175 """Make an wsgi application using hgweb, and my generated baseui
176 176 instance
177 177 """
178 178
179 179 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
180 180 return self.__load_web_settings(hgserve, self.extras)
181 181
182 182
183 183 def __check_permission(self, action, user, repo_name):
184 184 """Checks permissions using action (push/pull) user and repository
185 185 name
186 186
187 187 :param action: push or pull action
188 188 :param user: user instance
189 189 :param repo_name: repository name
190 190 """
191 191 if action == 'push':
192 192 if not HasPermissionAnyMiddleware('repository.write',
193 193 'repository.admin')\
194 194 (user, repo_name):
195 195 return False
196 196
197 197 else:
198 198 #any other action need at least read permission
199 199 if not HasPermissionAnyMiddleware('repository.read',
200 200 'repository.write',
201 201 'repository.admin')\
202 202 (user, repo_name):
203 203 return False
204 204
205 205 return True
206 206
207 207
208 208 def __get_repository(self, environ):
209 209 """Get's repository name out of PATH_INFO header
210 210
211 211 :param environ: environ where PATH_INFO is stored
212 212 """
213 213 try:
214 214 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
215 215 if repo_name.endswith('/'):
216 216 repo_name = repo_name.rstrip('/')
217 217 except:
218 218 log.error(traceback.format_exc())
219 219 raise
220 220
221 221 return repo_name
222 222
223 223 def __get_user(self, username):
224 224 return UserModel().get_by_username(username, cache=True)
225 225
226 226 def __get_action(self, environ):
227 227 """Maps mercurial request commands into a clone,pull or push command.
228 228 This should always return a valid command string
229 229
230 230 :param environ:
231 231 """
232 232 mapping = {'changegroup': 'pull',
233 233 'changegroupsubset': 'pull',
234 234 'stream_out': 'pull',
235 235 'listkeys': 'pull',
236 236 'unbundle': 'push',
237 237 'pushkey': 'push', }
238 238 for qry in environ['QUERY_STRING'].split('&'):
239 239 if qry.startswith('cmd'):
240 240 cmd = qry.split('=')[-1]
241 241 if mapping.has_key(cmd):
242 242 return mapping[cmd]
243 243 else:
244 return cmd
244 return 'pull'
245 245
246 246 def __invalidate_cache(self, repo_name):
247 247 """we know that some change was made to repositories and we should
248 248 invalidate the cache to see the changes right away but only for
249 249 push requests"""
250 250 invalidate_cache('get_repo_cached_%s' % repo_name)
251 251
252 252
253 253 def __load_web_settings(self, hgserve, extras={}):
254 254 #set the global ui for hgserve instance passed
255 255 hgserve.repo.ui = self.baseui
256 256
257 257 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
258 258
259 259 #inject some additional parameters that will be available in ui
260 260 #for hooks
261 261 for k, v in extras.items():
262 262 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
263 263
264 264 repoui = make_ui('file', hgrc, False)
265 265
266 266 if repoui:
267 267 #overwrite our ui instance with the section from hgrc file
268 268 for section in ui_sections:
269 269 for k, v in repoui.configitems(section):
270 270 hgserve.repo.ui.setconfig(section, k, v)
271 271
272 272 return hgserve
General Comments 0
You need to be logged in to leave comments. Login now