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