##// END OF EJS Templates
removed duplicate code that set os.environ['RC_SCM_DATA']...
marcink -
r3823:972ad33c beta
parent child Browse files
Show More
@@ -1,337 +1,336
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, fix_PATH, get_server_url,\
82 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
83 _set_extras
83 _set_extras
84 from rhodecode.lib.base import BaseVCSController
84 from rhodecode.lib.base import BaseVCSController
85 from rhodecode.lib.auth import get_container_username
85 from rhodecode.lib.auth import get_container_username
86 from rhodecode.lib.utils import is_valid_repo, make_ui
86 from rhodecode.lib.utils import is_valid_repo, make_ui
87 from rhodecode.lib.compat import json
87 from rhodecode.lib.compat import json
88 from rhodecode.model.db import User, RhodeCodeUi
88 from rhodecode.model.db import User, RhodeCodeUi
89
89
90 log = logging.getLogger(__name__)
90 log = logging.getLogger(__name__)
91
91
92
92
93 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
93 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
94
94
95
95
96 def is_git(environ):
96 def is_git(environ):
97 path_info = environ['PATH_INFO']
97 path_info = environ['PATH_INFO']
98 isgit_path = GIT_PROTO_PAT.match(path_info)
98 isgit_path = GIT_PROTO_PAT.match(path_info)
99 log.debug('pathinfo: %s detected as GIT %s' % (
99 log.debug('pathinfo: %s detected as GIT %s' % (
100 path_info, isgit_path != None)
100 path_info, isgit_path != None)
101 )
101 )
102 return isgit_path
102 return isgit_path
103
103
104
104
105 class SimpleGit(BaseVCSController):
105 class SimpleGit(BaseVCSController):
106
106
107 def _handle_request(self, environ, start_response):
107 def _handle_request(self, environ, start_response):
108 if not is_git(environ):
108 if not is_git(environ):
109 return self.application(environ, start_response)
109 return self.application(environ, start_response)
110 if not self._check_ssl(environ, start_response):
110 if not self._check_ssl(environ, start_response):
111 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
111 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
112
112
113 ip_addr = self._get_ip_addr(environ)
113 ip_addr = self._get_ip_addr(environ)
114 username = None
114 username = None
115 self._git_first_op = False
115 self._git_first_op = False
116 # skip passing error to error controller
116 # skip passing error to error controller
117 environ['pylons.status_code_redirect'] = True
117 environ['pylons.status_code_redirect'] = True
118
118
119 #======================================================================
119 #======================================================================
120 # EXTRACT REPOSITORY NAME FROM ENV
120 # EXTRACT REPOSITORY NAME FROM ENV
121 #======================================================================
121 #======================================================================
122 try:
122 try:
123 repo_name = self.__get_repository(environ)
123 repo_name = self.__get_repository(environ)
124 log.debug('Extracted repo name is %s' % repo_name)
124 log.debug('Extracted repo name is %s' % repo_name)
125 except Exception:
125 except Exception:
126 return HTTPInternalServerError()(environ, start_response)
126 return HTTPInternalServerError()(environ, start_response)
127
127
128 # quick check if that dir exists...
128 # quick check if that dir exists...
129 if not is_valid_repo(repo_name, self.basepath, 'git'):
129 if not is_valid_repo(repo_name, self.basepath, 'git'):
130 return HTTPNotFound()(environ, start_response)
130 return HTTPNotFound()(environ, start_response)
131
131
132 #======================================================================
132 #======================================================================
133 # GET ACTION PULL or PUSH
133 # GET ACTION PULL or PUSH
134 #======================================================================
134 #======================================================================
135 action = self.__get_action(environ)
135 action = self.__get_action(environ)
136
136
137 #======================================================================
137 #======================================================================
138 # CHECK ANONYMOUS PERMISSION
138 # CHECK ANONYMOUS PERMISSION
139 #======================================================================
139 #======================================================================
140 if action in ['pull', 'push']:
140 if action in ['pull', 'push']:
141 anonymous_user = self.__get_user('default')
141 anonymous_user = self.__get_user('default')
142 username = anonymous_user.username
142 username = anonymous_user.username
143 anonymous_perm = self._check_permission(action, anonymous_user,
143 anonymous_perm = self._check_permission(action, anonymous_user,
144 repo_name, ip_addr)
144 repo_name, ip_addr)
145
145
146 if not anonymous_perm or not anonymous_user.active:
146 if not anonymous_perm or not anonymous_user.active:
147 if not anonymous_perm:
147 if not anonymous_perm:
148 log.debug('Not enough credentials to access this '
148 log.debug('Not enough credentials to access this '
149 'repository as anonymous user')
149 'repository as anonymous user')
150 if not anonymous_user.active:
150 if not anonymous_user.active:
151 log.debug('Anonymous access is disabled, running '
151 log.debug('Anonymous access is disabled, running '
152 'authentication')
152 'authentication')
153 #==============================================================
153 #==============================================================
154 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
154 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
155 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
155 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
156 #==============================================================
156 #==============================================================
157
157
158 # Attempting to retrieve username from the container
158 # Attempting to retrieve username from the container
159 username = get_container_username(environ, self.config)
159 username = get_container_username(environ, self.config)
160
160
161 # If not authenticated by the container, running basic auth
161 # If not authenticated by the container, running basic auth
162 if not username:
162 if not username:
163 self.authenticate.realm = \
163 self.authenticate.realm = \
164 safe_str(self.config['rhodecode_realm'])
164 safe_str(self.config['rhodecode_realm'])
165 result = self.authenticate(environ)
165 result = self.authenticate(environ)
166 if isinstance(result, str):
166 if isinstance(result, str):
167 AUTH_TYPE.update(environ, 'basic')
167 AUTH_TYPE.update(environ, 'basic')
168 REMOTE_USER.update(environ, result)
168 REMOTE_USER.update(environ, result)
169 username = result
169 username = result
170 else:
170 else:
171 return result.wsgi_application(environ, start_response)
171 return result.wsgi_application(environ, start_response)
172
172
173 #==============================================================
173 #==============================================================
174 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
174 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
175 #==============================================================
175 #==============================================================
176 try:
176 try:
177 user = self.__get_user(username)
177 user = self.__get_user(username)
178 if user is None or not user.active:
178 if user is None or not user.active:
179 return HTTPForbidden()(environ, start_response)
179 return HTTPForbidden()(environ, start_response)
180 username = user.username
180 username = user.username
181 except Exception:
181 except Exception:
182 log.error(traceback.format_exc())
182 log.error(traceback.format_exc())
183 return HTTPInternalServerError()(environ, start_response)
183 return HTTPInternalServerError()(environ, start_response)
184
184
185 #check permissions for this repository
185 #check permissions for this repository
186 perm = self._check_permission(action, user, repo_name, ip_addr)
186 perm = self._check_permission(action, user, repo_name, ip_addr)
187 if not perm:
187 if not perm:
188 return HTTPForbidden()(environ, start_response)
188 return HTTPForbidden()(environ, start_response)
189
189
190 # extras are injected into UI object and later available
190 # extras are injected into UI object and later available
191 # in hooks executed by rhodecode
191 # in hooks executed by rhodecode
192 from rhodecode import CONFIG
192 from rhodecode import CONFIG
193 server_url = get_server_url(environ)
193 server_url = get_server_url(environ)
194 extras = {
194 extras = {
195 'ip': ip_addr,
195 'ip': ip_addr,
196 'username': username,
196 'username': username,
197 'action': action,
197 'action': action,
198 'repository': repo_name,
198 'repository': repo_name,
199 'scm': 'git',
199 'scm': 'git',
200 'config': CONFIG['__file__'],
200 'config': CONFIG['__file__'],
201 'server_url': server_url,
201 'server_url': server_url,
202 'make_lock': None,
202 'make_lock': None,
203 'locked_by': [None, None]
203 'locked_by': [None, None]
204 }
204 }
205
205
206 #===================================================================
206 #===================================================================
207 # GIT REQUEST HANDLING
207 # GIT REQUEST HANDLING
208 #===================================================================
208 #===================================================================
209 str_repo_name = safe_str(repo_name)
209 str_repo_name = safe_str(repo_name)
210 repo_path = os.path.join(safe_str(self.basepath),str_repo_name)
210 repo_path = os.path.join(safe_str(self.basepath),str_repo_name)
211 log.debug('Repository path is %s' % repo_path)
211 log.debug('Repository path is %s' % repo_path)
212
212
213 # CHECK LOCKING only if it's not ANONYMOUS USER
213 # CHECK LOCKING only if it's not ANONYMOUS USER
214 if username != User.DEFAULT_USER:
214 if username != User.DEFAULT_USER:
215 log.debug('Checking locking on repository')
215 log.debug('Checking locking on repository')
216 (make_lock,
216 (make_lock,
217 locked,
217 locked,
218 locked_by) = self._check_locking_state(
218 locked_by) = self._check_locking_state(
219 environ=environ, action=action,
219 environ=environ, action=action,
220 repo=repo_name, user_id=user.user_id
220 repo=repo_name, user_id=user.user_id
221 )
221 )
222 # store the make_lock for later evaluation in hooks
222 # store the make_lock for later evaluation in hooks
223 extras.update({'make_lock': make_lock,
223 extras.update({'make_lock': make_lock,
224 'locked_by': locked_by})
224 'locked_by': locked_by})
225 # set the environ variables for this request
225
226 os.environ['RC_SCM_DATA'] = json.dumps(extras)
227 fix_PATH()
226 fix_PATH()
228 log.debug('HOOKS extras is %s' % extras)
227 log.debug('HOOKS extras is %s' % extras)
229 baseui = make_ui('db')
228 baseui = make_ui('db')
230 self.__inject_extras(repo_path, baseui, extras)
229 self.__inject_extras(repo_path, baseui, extras)
231
230
232 try:
231 try:
233 self._handle_githooks(repo_name, action, baseui, environ)
232 self._handle_githooks(repo_name, action, baseui, environ)
234 log.info('%s action on GIT repo "%s" by "%s" from %s' %
233 log.info('%s action on GIT repo "%s" by "%s" from %s' %
235 (action, str_repo_name, safe_str(username), ip_addr))
234 (action, str_repo_name, safe_str(username), ip_addr))
236 app = self.__make_app(repo_name, repo_path, extras)
235 app = self.__make_app(repo_name, repo_path, extras)
237 return app(environ, start_response)
236 return app(environ, start_response)
238 except HTTPLockedRC, e:
237 except HTTPLockedRC, e:
239 _code = CONFIG.get('lock_ret_code')
238 _code = CONFIG.get('lock_ret_code')
240 log.debug('Repository LOCKED ret code %s!' % (_code))
239 log.debug('Repository LOCKED ret code %s!' % (_code))
241 return e(environ, start_response)
240 return e(environ, start_response)
242 except Exception:
241 except Exception:
243 log.error(traceback.format_exc())
242 log.error(traceback.format_exc())
244 return HTTPInternalServerError()(environ, start_response)
243 return HTTPInternalServerError()(environ, start_response)
245 finally:
244 finally:
246 # invalidate cache on push
245 # invalidate cache on push
247 if action == 'push':
246 if action == 'push':
248 self._invalidate_cache(repo_name)
247 self._invalidate_cache(repo_name)
249
248
250 def __make_app(self, repo_name, repo_path, extras):
249 def __make_app(self, repo_name, repo_path, extras):
251 """
250 """
252 Make an wsgi application using dulserver
251 Make an wsgi application using dulserver
253
252
254 :param repo_name: name of the repository
253 :param repo_name: name of the repository
255 :param repo_path: full path to the repository
254 :param repo_path: full path to the repository
256 """
255 """
257
256
258 from rhodecode.lib.middleware.pygrack import make_wsgi_app
257 from rhodecode.lib.middleware.pygrack import make_wsgi_app
259 app = make_wsgi_app(
258 app = make_wsgi_app(
260 repo_root=safe_str(self.basepath),
259 repo_root=safe_str(self.basepath),
261 repo_name=repo_name,
260 repo_name=repo_name,
262 extras=extras,
261 extras=extras,
263 )
262 )
264 app = GunzipFilter(LimitedInputFilter(app))
263 app = GunzipFilter(LimitedInputFilter(app))
265 return app
264 return app
266
265
267 def __get_repository(self, environ):
266 def __get_repository(self, environ):
268 """
267 """
269 Get's repository name out of PATH_INFO header
268 Get's repository name out of PATH_INFO header
270
269
271 :param environ: environ where PATH_INFO is stored
270 :param environ: environ where PATH_INFO is stored
272 """
271 """
273 try:
272 try:
274 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
273 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
275 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
274 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
276 except Exception:
275 except Exception:
277 log.error(traceback.format_exc())
276 log.error(traceback.format_exc())
278 raise
277 raise
279
278
280 return repo_name
279 return repo_name
281
280
282 def __get_user(self, username):
281 def __get_user(self, username):
283 return User.get_by_username(username)
282 return User.get_by_username(username)
284
283
285 def __get_action(self, environ):
284 def __get_action(self, environ):
286 """
285 """
287 Maps git request commands into a pull or push command.
286 Maps git request commands into a pull or push command.
288
287
289 :param environ:
288 :param environ:
290 """
289 """
291 service = environ['QUERY_STRING'].split('=')
290 service = environ['QUERY_STRING'].split('=')
292
291
293 if len(service) > 1:
292 if len(service) > 1:
294 service_cmd = service[1]
293 service_cmd = service[1]
295 mapping = {
294 mapping = {
296 'git-receive-pack': 'push',
295 'git-receive-pack': 'push',
297 'git-upload-pack': 'pull',
296 'git-upload-pack': 'pull',
298 }
297 }
299 op = mapping[service_cmd]
298 op = mapping[service_cmd]
300 self._git_stored_op = op
299 self._git_stored_op = op
301 return op
300 return op
302 else:
301 else:
303 # try to fallback to stored variable as we don't know if the last
302 # try to fallback to stored variable as we don't know if the last
304 # operation is pull/push
303 # operation is pull/push
305 op = getattr(self, '_git_stored_op', 'pull')
304 op = getattr(self, '_git_stored_op', 'pull')
306 return op
305 return op
307
306
308 def _handle_githooks(self, repo_name, action, baseui, environ):
307 def _handle_githooks(self, repo_name, action, baseui, environ):
309 """
308 """
310 Handles pull action, push is handled by post-receive hook
309 Handles pull action, push is handled by post-receive hook
311 """
310 """
312 from rhodecode.lib.hooks import log_pull_action
311 from rhodecode.lib.hooks import log_pull_action
313 service = environ['QUERY_STRING'].split('=')
312 service = environ['QUERY_STRING'].split('=')
314
313
315 if len(service) < 2:
314 if len(service) < 2:
316 return
315 return
317
316
318 from rhodecode.model.db import Repository
317 from rhodecode.model.db import Repository
319 _repo = Repository.get_by_repo_name(repo_name)
318 _repo = Repository.get_by_repo_name(repo_name)
320 _repo = _repo.scm_instance
319 _repo = _repo.scm_instance
321
320
322 _hooks = dict(baseui.configitems('hooks')) or {}
321 _hooks = dict(baseui.configitems('hooks')) or {}
323 if action == 'pull':
322 if action == 'pull':
324 # stupid git, emulate pre-pull hook !
323 # stupid git, emulate pre-pull hook !
325 pre_pull(ui=baseui, repo=_repo._repo)
324 pre_pull(ui=baseui, repo=_repo._repo)
326 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
325 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
327 log_pull_action(ui=baseui, repo=_repo._repo)
326 log_pull_action(ui=baseui, repo=_repo._repo)
328
327
329 def __inject_extras(self, repo_path, baseui, extras={}):
328 def __inject_extras(self, repo_path, baseui, extras={}):
330 """
329 """
331 Injects some extra params into baseui instance
330 Injects some extra params into baseui instance
332
331
333 :param baseui: baseui instance
332 :param baseui: baseui instance
334 :param extras: dict with extra params to put into baseui
333 :param extras: dict with extra params to put into baseui
335 """
334 """
336
335
337 _set_extras(extras)
336 _set_extras(extras)
@@ -1,288 +1,286
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). 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 logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from mercurial.error import RepoError
31 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
32 from mercurial.hgweb import hgweb_mod
33
33
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
35 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
36 HTTPBadRequest, HTTPNotAcceptable
36 HTTPBadRequest, HTTPNotAcceptable
37
37
38 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
38 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
39 _set_extras
39 _set_extras
40 from rhodecode.lib.base import BaseVCSController
40 from rhodecode.lib.base import BaseVCSController
41 from rhodecode.lib.auth import get_container_username
41 from rhodecode.lib.auth import get_container_username
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 from rhodecode.lib.compat import json
43 from rhodecode.lib.compat import json
44 from rhodecode.model.db import User
44 from rhodecode.model.db import User
45 from rhodecode.lib.exceptions import HTTPLockedRC
45 from rhodecode.lib.exceptions import HTTPLockedRC
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def is_mercurial(environ):
51 def is_mercurial(environ):
52 """
52 """
53 Returns True if request's target is mercurial server - header
53 Returns True if request's target is mercurial server - header
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
55 """
55 """
56 http_accept = environ.get('HTTP_ACCEPT')
56 http_accept = environ.get('HTTP_ACCEPT')
57 path_info = environ['PATH_INFO']
57 path_info = environ['PATH_INFO']
58 if http_accept and http_accept.startswith('application/mercurial'):
58 if http_accept and http_accept.startswith('application/mercurial'):
59 ishg_path = True
59 ishg_path = True
60 else:
60 else:
61 ishg_path = False
61 ishg_path = False
62
62
63 log.debug('pathinfo: %s detected as HG %s' % (
63 log.debug('pathinfo: %s detected as HG %s' % (
64 path_info, ishg_path)
64 path_info, ishg_path)
65 )
65 )
66 return ishg_path
66 return ishg_path
67
67
68
68
69 class SimpleHg(BaseVCSController):
69 class SimpleHg(BaseVCSController):
70
70
71 def _handle_request(self, environ, start_response):
71 def _handle_request(self, environ, start_response):
72 if not is_mercurial(environ):
72 if not is_mercurial(environ):
73 return self.application(environ, start_response)
73 return self.application(environ, start_response)
74 if not self._check_ssl(environ, start_response):
74 if not self._check_ssl(environ, start_response):
75 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
75 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
76
76
77 ip_addr = self._get_ip_addr(environ)
77 ip_addr = self._get_ip_addr(environ)
78 username = None
78 username = None
79 # skip passing error to error controller
79 # skip passing error to error controller
80 environ['pylons.status_code_redirect'] = True
80 environ['pylons.status_code_redirect'] = True
81
81
82 #======================================================================
82 #======================================================================
83 # EXTRACT REPOSITORY NAME FROM ENV
83 # EXTRACT REPOSITORY NAME FROM ENV
84 #======================================================================
84 #======================================================================
85 try:
85 try:
86 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
86 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
87 log.debug('Extracted repo name is %s' % repo_name)
87 log.debug('Extracted repo name is %s' % repo_name)
88 except Exception:
88 except Exception:
89 return HTTPInternalServerError()(environ, start_response)
89 return HTTPInternalServerError()(environ, start_response)
90
90
91 # quick check if that dir exists...
91 # quick check if that dir exists...
92 if not is_valid_repo(repo_name, self.basepath, 'hg'):
92 if not is_valid_repo(repo_name, self.basepath, 'hg'):
93 return HTTPNotFound()(environ, start_response)
93 return HTTPNotFound()(environ, start_response)
94
94
95 #======================================================================
95 #======================================================================
96 # GET ACTION PULL or PUSH
96 # GET ACTION PULL or PUSH
97 #======================================================================
97 #======================================================================
98 action = self.__get_action(environ)
98 action = self.__get_action(environ)
99
99
100 #======================================================================
100 #======================================================================
101 # CHECK ANONYMOUS PERMISSION
101 # CHECK ANONYMOUS PERMISSION
102 #======================================================================
102 #======================================================================
103 if action in ['pull', 'push']:
103 if action in ['pull', 'push']:
104 anonymous_user = self.__get_user('default')
104 anonymous_user = self.__get_user('default')
105 username = anonymous_user.username
105 username = anonymous_user.username
106 anonymous_perm = self._check_permission(action, anonymous_user,
106 anonymous_perm = self._check_permission(action, anonymous_user,
107 repo_name, ip_addr)
107 repo_name, ip_addr)
108
108
109 if not anonymous_perm or not anonymous_user.active:
109 if not anonymous_perm or not anonymous_user.active:
110 if not anonymous_perm:
110 if not anonymous_perm:
111 log.debug('Not enough credentials to access this '
111 log.debug('Not enough credentials to access this '
112 'repository as anonymous user')
112 'repository as anonymous user')
113 if not anonymous_user.active:
113 if not anonymous_user.active:
114 log.debug('Anonymous access is disabled, running '
114 log.debug('Anonymous access is disabled, running '
115 'authentication')
115 'authentication')
116 #==============================================================
116 #==============================================================
117 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
117 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
118 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
118 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
119 #==============================================================
119 #==============================================================
120
120
121 # Attempting to retrieve username from the container
121 # Attempting to retrieve username from the container
122 username = get_container_username(environ, self.config)
122 username = get_container_username(environ, self.config)
123
123
124 # If not authenticated by the container, running basic auth
124 # If not authenticated by the container, running basic auth
125 if not username:
125 if not username:
126 self.authenticate.realm = \
126 self.authenticate.realm = \
127 safe_str(self.config['rhodecode_realm'])
127 safe_str(self.config['rhodecode_realm'])
128 result = self.authenticate(environ)
128 result = self.authenticate(environ)
129 if isinstance(result, str):
129 if isinstance(result, str):
130 AUTH_TYPE.update(environ, 'basic')
130 AUTH_TYPE.update(environ, 'basic')
131 REMOTE_USER.update(environ, result)
131 REMOTE_USER.update(environ, result)
132 username = result
132 username = result
133 else:
133 else:
134 return result.wsgi_application(environ, start_response)
134 return result.wsgi_application(environ, start_response)
135
135
136 #==============================================================
136 #==============================================================
137 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
137 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
138 #==============================================================
138 #==============================================================
139 try:
139 try:
140 user = self.__get_user(username)
140 user = self.__get_user(username)
141 if user is None or not user.active:
141 if user is None or not user.active:
142 return HTTPForbidden()(environ, start_response)
142 return HTTPForbidden()(environ, start_response)
143 username = user.username
143 username = user.username
144 except Exception:
144 except Exception:
145 log.error(traceback.format_exc())
145 log.error(traceback.format_exc())
146 return HTTPInternalServerError()(environ, start_response)
146 return HTTPInternalServerError()(environ, start_response)
147
147
148 #check permissions for this repository
148 #check permissions for this repository
149 perm = self._check_permission(action, user, repo_name, ip_addr)
149 perm = self._check_permission(action, user, repo_name, ip_addr)
150 if not perm:
150 if not perm:
151 return HTTPForbidden()(environ, start_response)
151 return HTTPForbidden()(environ, start_response)
152
152
153 # extras are injected into mercurial UI object and later available
153 # extras are injected into mercurial UI object and later available
154 # in hg hooks executed by rhodecode
154 # in hg hooks executed by rhodecode
155 from rhodecode import CONFIG
155 from rhodecode import CONFIG
156 server_url = get_server_url(environ)
156 server_url = get_server_url(environ)
157 extras = {
157 extras = {
158 'ip': ip_addr,
158 'ip': ip_addr,
159 'username': username,
159 'username': username,
160 'action': action,
160 'action': action,
161 'repository': repo_name,
161 'repository': repo_name,
162 'scm': 'hg',
162 'scm': 'hg',
163 'config': CONFIG['__file__'],
163 'config': CONFIG['__file__'],
164 'server_url': server_url,
164 'server_url': server_url,
165 'make_lock': None,
165 'make_lock': None,
166 'locked_by': [None, None]
166 'locked_by': [None, None]
167 }
167 }
168 #======================================================================
168 #======================================================================
169 # MERCURIAL REQUEST HANDLING
169 # MERCURIAL REQUEST HANDLING
170 #======================================================================
170 #======================================================================
171 str_repo_name = safe_str(repo_name)
171 str_repo_name = safe_str(repo_name)
172 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
172 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
173 log.debug('Repository path is %s' % repo_path)
173 log.debug('Repository path is %s' % repo_path)
174
174
175 # CHECK LOCKING only if it's not ANONYMOUS USER
175 # CHECK LOCKING only if it's not ANONYMOUS USER
176 if username != User.DEFAULT_USER:
176 if username != User.DEFAULT_USER:
177 log.debug('Checking locking on repository')
177 log.debug('Checking locking on repository')
178 (make_lock,
178 (make_lock,
179 locked,
179 locked,
180 locked_by) = self._check_locking_state(
180 locked_by) = self._check_locking_state(
181 environ=environ, action=action,
181 environ=environ, action=action,
182 repo=repo_name, user_id=user.user_id
182 repo=repo_name, user_id=user.user_id
183 )
183 )
184 # store the make_lock for later evaluation in hooks
184 # store the make_lock for later evaluation in hooks
185 extras.update({'make_lock': make_lock,
185 extras.update({'make_lock': make_lock,
186 'locked_by': locked_by})
186 'locked_by': locked_by})
187
187
188 # set the environ variables for this request
189 os.environ['RC_SCM_DATA'] = json.dumps(extras)
190 fix_PATH()
188 fix_PATH()
191 log.debug('HOOKS extras is %s' % extras)
189 log.debug('HOOKS extras is %s' % extras)
192 baseui = make_ui('db')
190 baseui = make_ui('db')
193 self.__inject_extras(repo_path, baseui, extras)
191 self.__inject_extras(repo_path, baseui, extras)
194
192
195 try:
193 try:
196 log.info('%s action on HG repo "%s" by "%s" from %s' %
194 log.info('%s action on HG repo "%s" by "%s" from %s' %
197 (action, str_repo_name, safe_str(username), ip_addr))
195 (action, str_repo_name, safe_str(username), ip_addr))
198 app = self.__make_app(repo_path, baseui, extras)
196 app = self.__make_app(repo_path, baseui, extras)
199 return app(environ, start_response)
197 return app(environ, start_response)
200 except RepoError, e:
198 except RepoError, e:
201 if str(e).find('not found') != -1:
199 if str(e).find('not found') != -1:
202 return HTTPNotFound()(environ, start_response)
200 return HTTPNotFound()(environ, start_response)
203 except HTTPLockedRC, e:
201 except HTTPLockedRC, e:
204 _code = CONFIG.get('lock_ret_code')
202 _code = CONFIG.get('lock_ret_code')
205 log.debug('Repository LOCKED ret code %s!' % (_code))
203 log.debug('Repository LOCKED ret code %s!' % (_code))
206 return e(environ, start_response)
204 return e(environ, start_response)
207 except Exception:
205 except Exception:
208 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
209 return HTTPInternalServerError()(environ, start_response)
207 return HTTPInternalServerError()(environ, start_response)
210 finally:
208 finally:
211 # invalidate cache on push
209 # invalidate cache on push
212 if action == 'push':
210 if action == 'push':
213 self._invalidate_cache(repo_name)
211 self._invalidate_cache(repo_name)
214
212
215 def __make_app(self, repo_name, baseui, extras):
213 def __make_app(self, repo_name, baseui, extras):
216 """
214 """
217 Make an wsgi application using hgweb, and inject generated baseui
215 Make an wsgi application using hgweb, and inject generated baseui
218 instance, additionally inject some extras into ui object
216 instance, additionally inject some extras into ui object
219 """
217 """
220 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
218 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
221
219
222 def __get_repository(self, environ):
220 def __get_repository(self, environ):
223 """
221 """
224 Get's repository name out of PATH_INFO header
222 Get's repository name out of PATH_INFO header
225
223
226 :param environ: environ where PATH_INFO is stored
224 :param environ: environ where PATH_INFO is stored
227 """
225 """
228 try:
226 try:
229 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
227 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
230 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
228 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
231 if repo_name.endswith('/'):
229 if repo_name.endswith('/'):
232 repo_name = repo_name.rstrip('/')
230 repo_name = repo_name.rstrip('/')
233 except Exception:
231 except Exception:
234 log.error(traceback.format_exc())
232 log.error(traceback.format_exc())
235 raise
233 raise
236
234
237 return repo_name
235 return repo_name
238
236
239 def __get_user(self, username):
237 def __get_user(self, username):
240 return User.get_by_username(username)
238 return User.get_by_username(username)
241
239
242 def __get_action(self, environ):
240 def __get_action(self, environ):
243 """
241 """
244 Maps mercurial request commands into a clone,pull or push command.
242 Maps mercurial request commands into a clone,pull or push command.
245 This should always return a valid command string
243 This should always return a valid command string
246
244
247 :param environ:
245 :param environ:
248 """
246 """
249 mapping = {'changegroup': 'pull',
247 mapping = {'changegroup': 'pull',
250 'changegroupsubset': 'pull',
248 'changegroupsubset': 'pull',
251 'stream_out': 'pull',
249 'stream_out': 'pull',
252 'listkeys': 'pull',
250 'listkeys': 'pull',
253 'unbundle': 'push',
251 'unbundle': 'push',
254 'pushkey': 'push', }
252 'pushkey': 'push', }
255 for qry in environ['QUERY_STRING'].split('&'):
253 for qry in environ['QUERY_STRING'].split('&'):
256 if qry.startswith('cmd'):
254 if qry.startswith('cmd'):
257 cmd = qry.split('=')[-1]
255 cmd = qry.split('=')[-1]
258 if cmd in mapping:
256 if cmd in mapping:
259 return mapping[cmd]
257 return mapping[cmd]
260
258
261 return 'pull'
259 return 'pull'
262
260
263 raise Exception('Unable to detect pull/push action !!'
261 raise Exception('Unable to detect pull/push action !!'
264 'Are you using non standard command or client ?')
262 'Are you using non standard command or client ?')
265
263
266 def __inject_extras(self, repo_path, baseui, extras={}):
264 def __inject_extras(self, repo_path, baseui, extras={}):
267 """
265 """
268 Injects some extra params into baseui instance
266 Injects some extra params into baseui instance
269
267
270 also overwrites global settings with those takes from local hgrc file
268 also overwrites global settings with those takes from local hgrc file
271
269
272 :param baseui: baseui instance
270 :param baseui: baseui instance
273 :param extras: dict with extra params to put into baseui
271 :param extras: dict with extra params to put into baseui
274 """
272 """
275
273
276 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
274 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
277
275
278 # make our hgweb quiet so it doesn't print output
276 # make our hgweb quiet so it doesn't print output
279 baseui.setconfig('ui', 'quiet', 'true')
277 baseui.setconfig('ui', 'quiet', 'true')
280
278
281 repoui = make_ui('file', hgrc, False)
279 repoui = make_ui('file', hgrc, False)
282
280
283 if repoui:
281 if repoui:
284 #overwrite our ui instance with the section from hgrc file
282 #overwrite our ui instance with the section from hgrc file
285 for section in ui_sections:
283 for section in ui_sections:
286 for k, v in repoui.configitems(section):
284 for k, v in repoui.configitems(section):
287 baseui.setconfig(section, k, v)
285 baseui.setconfig(section, k, v)
288 _set_extras(extras)
286 _set_extras(extras)
General Comments 0
You need to be logged in to leave comments. Login now