##// END OF EJS Templates
don't invalidate cache before handling hook
marcink -
r3277:5440ea1d beta
parent child Browse files
Show More
@@ -1,341 +1,340 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 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 modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import re
29 29 import logging
30 30 import traceback
31 31
32 32 from dulwich import server as dulserver
33 33 from dulwich.web import LimitedInputFilter, GunzipFilter
34 34 from rhodecode.lib.exceptions import HTTPLockedRC
35 35 from rhodecode.lib.hooks import pre_pull
36 36
37 37
38 38 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
39 39
40 40 def handle(self):
41 41 write = lambda x: self.proto.write_sideband(1, x)
42 42
43 43 graph_walker = dulserver.ProtocolGraphWalker(self,
44 44 self.repo.object_store,
45 45 self.repo.get_peeled)
46 46 objects_iter = self.repo.fetch_objects(
47 47 graph_walker.determine_wants, graph_walker, self.progress,
48 48 get_tagged=self.get_tagged)
49 49
50 50 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
51 51 # that the client still expects a 0-object pack in most cases.
52 52 if objects_iter is None:
53 53 return
54 54
55 55 self.progress("counting objects: %d, done.\n" % len(objects_iter))
56 56 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
57 57 objects_iter)
58 58 messages = []
59 59 messages.append('thank you for using rhodecode')
60 60
61 61 for msg in messages:
62 62 self.progress(msg + "\n")
63 63 # we are done
64 64 self.proto.write("0000")
65 65
66 66
67 67 dulserver.DEFAULT_HANDLERS = {
68 68 #git-ls-remote, git-clone, git-fetch and git-pull
69 69 'git-upload-pack': SimpleGitUploadPackHandler,
70 70 #git-push
71 71 'git-receive-pack': dulserver.ReceivePackHandler,
72 72 }
73 73
74 74 # not used for now until dulwich get's fixed
75 75 #from dulwich.repo import Repo
76 76 #from dulwich.web import make_wsgi_chain
77 77
78 78 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
79 79 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
80 80 HTTPBadRequest, HTTPNotAcceptable
81 81
82 82 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url
83 83 from rhodecode.lib.base import BaseVCSController
84 84 from rhodecode.lib.auth import get_container_username
85 85 from rhodecode.lib.utils import is_valid_repo, make_ui
86 86 from rhodecode.lib.compat import json
87 87 from rhodecode.model.db import User, RhodeCodeUi
88 88
89 89 log = logging.getLogger(__name__)
90 90
91 91
92 92 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
93 93
94 94
95 95 def is_git(environ):
96 96 path_info = environ['PATH_INFO']
97 97 isgit_path = GIT_PROTO_PAT.match(path_info)
98 98 log.debug('pathinfo: %s detected as GIT %s' % (
99 99 path_info, isgit_path != None)
100 100 )
101 101 return isgit_path
102 102
103 103
104 104 class SimpleGit(BaseVCSController):
105 105
106 106 def _handle_request(self, environ, start_response):
107 107 if not is_git(environ):
108 108 return self.application(environ, start_response)
109 109 if not self._check_ssl(environ, start_response):
110 110 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
111 111
112 112 ip_addr = self._get_ip_addr(environ)
113 113 username = None
114 114 self._git_first_op = False
115 115 # skip passing error to error controller
116 116 environ['pylons.status_code_redirect'] = True
117 117
118 118 #======================================================================
119 119 # EXTRACT REPOSITORY NAME FROM ENV
120 120 #======================================================================
121 121 try:
122 122 repo_name = self.__get_repository(environ)
123 123 log.debug('Extracted repo name is %s' % repo_name)
124 124 except:
125 125 return HTTPInternalServerError()(environ, start_response)
126 126
127 127 # quick check if that dir exists...
128 128 if is_valid_repo(repo_name, self.basepath, 'git') is False:
129 129 return HTTPNotFound()(environ, start_response)
130 130
131 131 #======================================================================
132 132 # GET ACTION PULL or PUSH
133 133 #======================================================================
134 134 action = self.__get_action(environ)
135 135
136 136 #======================================================================
137 137 # CHECK ANONYMOUS PERMISSION
138 138 #======================================================================
139 139 if action in ['pull', 'push']:
140 140 anonymous_user = self.__get_user('default')
141 141 username = anonymous_user.username
142 142 anonymous_perm = self._check_permission(action, anonymous_user,
143 143 repo_name, ip_addr)
144 144
145 145 if anonymous_perm is not True or anonymous_user.active is False:
146 146 if anonymous_perm is not True:
147 147 log.debug('Not enough credentials to access this '
148 148 'repository as anonymous user')
149 149 if anonymous_user.active is False:
150 150 log.debug('Anonymous access is disabled, running '
151 151 'authentication')
152 152 #==============================================================
153 153 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
154 154 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
155 155 #==============================================================
156 156
157 157 # Attempting to retrieve username from the container
158 158 username = get_container_username(environ, self.config)
159 159
160 160 # If not authenticated by the container, running basic auth
161 161 if not username:
162 162 self.authenticate.realm = \
163 163 safe_str(self.config['rhodecode_realm'])
164 164 result = self.authenticate(environ)
165 165 if isinstance(result, str):
166 166 AUTH_TYPE.update(environ, 'basic')
167 167 REMOTE_USER.update(environ, result)
168 168 username = result
169 169 else:
170 170 return result.wsgi_application(environ, start_response)
171 171
172 172 #==============================================================
173 173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
174 174 #==============================================================
175 175 try:
176 176 user = self.__get_user(username)
177 177 if user is None or not user.active:
178 178 return HTTPForbidden()(environ, start_response)
179 179 username = user.username
180 180 except:
181 181 log.error(traceback.format_exc())
182 182 return HTTPInternalServerError()(environ, start_response)
183 183
184 184 #check permissions for this repository
185 185 perm = self._check_permission(action, user, repo_name, ip_addr)
186 186 if perm is not True:
187 187 return HTTPForbidden()(environ, start_response)
188 188
189 189 # extras are injected into UI object and later available
190 190 # in hooks executed by rhodecode
191 191 from rhodecode import CONFIG
192 192 server_url = get_server_url(environ)
193 193 extras = {
194 194 'ip': ip_addr,
195 195 'username': username,
196 196 'action': action,
197 197 'repository': repo_name,
198 198 'scm': 'git',
199 199 'config': CONFIG['__file__'],
200 200 'server_url': server_url,
201 201 'make_lock': None,
202 202 'locked_by': [None, None]
203 203 }
204 204
205 205 #===================================================================
206 206 # GIT REQUEST HANDLING
207 207 #===================================================================
208 208 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
209 209 log.debug('Repository path is %s' % repo_path)
210 210
211 211 # CHECK LOCKING only if it's not ANONYMOUS USER
212 212 if username != User.DEFAULT_USER:
213 213 log.debug('Checking locking on repository')
214 214 (make_lock,
215 215 locked,
216 216 locked_by) = self._check_locking_state(
217 217 environ=environ, action=action,
218 218 repo=repo_name, user_id=user.user_id
219 219 )
220 220 # store the make_lock for later evaluation in hooks
221 221 extras.update({'make_lock': make_lock,
222 222 'locked_by': locked_by})
223 223 # set the environ variables for this request
224 224 os.environ['RC_SCM_DATA'] = json.dumps(extras)
225 225 fix_PATH()
226 226 log.debug('HOOKS extras is %s' % extras)
227 227 baseui = make_ui('db')
228 228 self.__inject_extras(repo_path, baseui, extras)
229 229
230 230 try:
231 self._handle_githooks(repo_name, action, baseui, environ)
231 232 # invalidate cache on push
232 233 if action == 'push':
233 234 self._invalidate_cache(repo_name)
234 self._handle_githooks(repo_name, action, baseui, environ)
235
236 235 log.info('%s action on GIT repo "%s" by "%s" from %s' %
237 236 (action, repo_name, username, ip_addr))
238 237 app = self.__make_app(repo_name, repo_path, extras)
239 238 return app(environ, start_response)
240 239 except HTTPLockedRC, e:
241 240 log.debug('Repository LOCKED ret code 423!')
242 241 return e(environ, start_response)
243 242 except Exception:
244 243 log.error(traceback.format_exc())
245 244 return HTTPInternalServerError()(environ, start_response)
246 245
247 246 def __make_app(self, repo_name, repo_path, extras):
248 247 """
249 248 Make an wsgi application using dulserver
250 249
251 250 :param repo_name: name of the repository
252 251 :param repo_path: full path to the repository
253 252 """
254 253
255 254 from rhodecode.lib.middleware.pygrack import make_wsgi_app
256 255 app = make_wsgi_app(
257 256 repo_root=safe_str(self.basepath),
258 257 repo_name=repo_name,
259 258 extras=extras,
260 259 )
261 260 app = GunzipFilter(LimitedInputFilter(app))
262 261 return app
263 262
264 263 def __get_repository(self, environ):
265 264 """
266 265 Get's repository name out of PATH_INFO header
267 266
268 267 :param environ: environ where PATH_INFO is stored
269 268 """
270 269 try:
271 270 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
272 271 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
273 272 except:
274 273 log.error(traceback.format_exc())
275 274 raise
276 275
277 276 return repo_name
278 277
279 278 def __get_user(self, username):
280 279 return User.get_by_username(username)
281 280
282 281 def __get_action(self, environ):
283 282 """
284 283 Maps git request commands into a pull or push command.
285 284
286 285 :param environ:
287 286 """
288 287 service = environ['QUERY_STRING'].split('=')
289 288
290 289 if len(service) > 1:
291 290 service_cmd = service[1]
292 291 mapping = {
293 292 'git-receive-pack': 'push',
294 293 'git-upload-pack': 'pull',
295 294 }
296 295 op = mapping[service_cmd]
297 296 self._git_stored_op = op
298 297 return op
299 298 else:
300 299 # try to fallback to stored variable as we don't know if the last
301 300 # operation is pull/push
302 301 op = getattr(self, '_git_stored_op', 'pull')
303 302 return op
304 303
305 304 def _handle_githooks(self, repo_name, action, baseui, environ):
306 305 """
307 306 Handles pull action, push is handled by post-receive hook
308 307 """
309 308 from rhodecode.lib.hooks import log_pull_action
310 309 service = environ['QUERY_STRING'].split('=')
311 310
312 311 if len(service) < 2:
313 312 return
314 313
315 314 from rhodecode.model.db import Repository
316 315 _repo = Repository.get_by_repo_name(repo_name)
317 316 _repo = _repo.scm_instance
318 317 _repo._repo.ui = baseui
319 318
320 319 _hooks = dict(baseui.configitems('hooks')) or {}
321 320 if action == 'pull':
322 321 # stupid git, emulate pre-pull hook !
323 322 pre_pull(ui=baseui, repo=_repo._repo)
324 323 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
325 324 log_pull_action(ui=baseui, repo=_repo._repo)
326 325
327 326 def __inject_extras(self, repo_path, baseui, extras={}):
328 327 """
329 328 Injects some extra params into baseui instance
330 329
331 330 :param baseui: baseui instance
332 331 :param extras: dict with extra params to put into baseui
333 332 """
334 333
335 334 # make our hgweb quiet so it doesn't print output
336 335 baseui.setconfig('ui', 'quiet', 'true')
337 336
338 337 #inject some additional parameters that will be available in ui
339 338 #for hooks
340 339 for k, v in extras.items():
341 340 baseui.setconfig('rhodecode_extras', k, v)
General Comments 0
You need to be logged in to leave comments. Login now