##// END OF EJS Templates
fix unicode issues on logging in SCM middlewares
marcink -
r3634:336184b9 beta
parent child Browse files
Show More
@@ -1,336 +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, 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 repo_path = os.path.join(safe_str(self.basepath), 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 log.debug('Repository path is %s' % repo_path)
211 log.debug('Repository path is %s' % repo_path)
211
212
212 # CHECK LOCKING only if it's not ANONYMOUS USER
213 # CHECK LOCKING only if it's not ANONYMOUS USER
213 if username != User.DEFAULT_USER:
214 if username != User.DEFAULT_USER:
214 log.debug('Checking locking on repository')
215 log.debug('Checking locking on repository')
215 (make_lock,
216 (make_lock,
216 locked,
217 locked,
217 locked_by) = self._check_locking_state(
218 locked_by) = self._check_locking_state(
218 environ=environ, action=action,
219 environ=environ, action=action,
219 repo=repo_name, user_id=user.user_id
220 repo=repo_name, user_id=user.user_id
220 )
221 )
221 # store the make_lock for later evaluation in hooks
222 # store the make_lock for later evaluation in hooks
222 extras.update({'make_lock': make_lock,
223 extras.update({'make_lock': make_lock,
223 'locked_by': locked_by})
224 'locked_by': locked_by})
224 # set the environ variables for this request
225 # set the environ variables for this request
225 os.environ['RC_SCM_DATA'] = json.dumps(extras)
226 os.environ['RC_SCM_DATA'] = json.dumps(extras)
226 fix_PATH()
227 fix_PATH()
227 log.debug('HOOKS extras is %s' % extras)
228 log.debug('HOOKS extras is %s' % extras)
228 baseui = make_ui('db')
229 baseui = make_ui('db')
229 self.__inject_extras(repo_path, baseui, extras)
230 self.__inject_extras(repo_path, baseui, extras)
230
231
231 try:
232 try:
232 self._handle_githooks(repo_name, action, baseui, environ)
233 self._handle_githooks(repo_name, action, baseui, environ)
233 log.info('%s action on GIT repo "%s" by "%s" from %s' %
234 log.info('%s action on GIT repo "%s" by "%s" from %s' %
234 (action, repo_name, username, ip_addr))
235 (action, str_repo_name, safe_str(username), ip_addr))
235 app = self.__make_app(repo_name, repo_path, extras)
236 app = self.__make_app(repo_name, repo_path, extras)
236 return app(environ, start_response)
237 return app(environ, start_response)
237 except HTTPLockedRC, e:
238 except HTTPLockedRC, e:
238 _code = CONFIG.get('lock_ret_code')
239 _code = CONFIG.get('lock_ret_code')
239 log.debug('Repository LOCKED ret code %s!' % (_code))
240 log.debug('Repository LOCKED ret code %s!' % (_code))
240 return e(environ, start_response)
241 return e(environ, start_response)
241 except Exception:
242 except Exception:
242 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
243 return HTTPInternalServerError()(environ, start_response)
244 return HTTPInternalServerError()(environ, start_response)
244 finally:
245 finally:
245 # invalidate cache on push
246 # invalidate cache on push
246 if action == 'push':
247 if action == 'push':
247 self._invalidate_cache(repo_name)
248 self._invalidate_cache(repo_name)
248
249
249 def __make_app(self, repo_name, repo_path, extras):
250 def __make_app(self, repo_name, repo_path, extras):
250 """
251 """
251 Make an wsgi application using dulserver
252 Make an wsgi application using dulserver
252
253
253 :param repo_name: name of the repository
254 :param repo_name: name of the repository
254 :param repo_path: full path to the repository
255 :param repo_path: full path to the repository
255 """
256 """
256
257
257 from rhodecode.lib.middleware.pygrack import make_wsgi_app
258 from rhodecode.lib.middleware.pygrack import make_wsgi_app
258 app = make_wsgi_app(
259 app = make_wsgi_app(
259 repo_root=safe_str(self.basepath),
260 repo_root=safe_str(self.basepath),
260 repo_name=repo_name,
261 repo_name=repo_name,
261 extras=extras,
262 extras=extras,
262 )
263 )
263 app = GunzipFilter(LimitedInputFilter(app))
264 app = GunzipFilter(LimitedInputFilter(app))
264 return app
265 return app
265
266
266 def __get_repository(self, environ):
267 def __get_repository(self, environ):
267 """
268 """
268 Get's repository name out of PATH_INFO header
269 Get's repository name out of PATH_INFO header
269
270
270 :param environ: environ where PATH_INFO is stored
271 :param environ: environ where PATH_INFO is stored
271 """
272 """
272 try:
273 try:
273 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
274 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
274 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
275 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
275 except Exception:
276 except Exception:
276 log.error(traceback.format_exc())
277 log.error(traceback.format_exc())
277 raise
278 raise
278
279
279 return repo_name
280 return repo_name
280
281
281 def __get_user(self, username):
282 def __get_user(self, username):
282 return User.get_by_username(username)
283 return User.get_by_username(username)
283
284
284 def __get_action(self, environ):
285 def __get_action(self, environ):
285 """
286 """
286 Maps git request commands into a pull or push command.
287 Maps git request commands into a pull or push command.
287
288
288 :param environ:
289 :param environ:
289 """
290 """
290 service = environ['QUERY_STRING'].split('=')
291 service = environ['QUERY_STRING'].split('=')
291
292
292 if len(service) > 1:
293 if len(service) > 1:
293 service_cmd = service[1]
294 service_cmd = service[1]
294 mapping = {
295 mapping = {
295 'git-receive-pack': 'push',
296 'git-receive-pack': 'push',
296 'git-upload-pack': 'pull',
297 'git-upload-pack': 'pull',
297 }
298 }
298 op = mapping[service_cmd]
299 op = mapping[service_cmd]
299 self._git_stored_op = op
300 self._git_stored_op = op
300 return op
301 return op
301 else:
302 else:
302 # try to fallback to stored variable as we don't know if the last
303 # try to fallback to stored variable as we don't know if the last
303 # operation is pull/push
304 # operation is pull/push
304 op = getattr(self, '_git_stored_op', 'pull')
305 op = getattr(self, '_git_stored_op', 'pull')
305 return op
306 return op
306
307
307 def _handle_githooks(self, repo_name, action, baseui, environ):
308 def _handle_githooks(self, repo_name, action, baseui, environ):
308 """
309 """
309 Handles pull action, push is handled by post-receive hook
310 Handles pull action, push is handled by post-receive hook
310 """
311 """
311 from rhodecode.lib.hooks import log_pull_action
312 from rhodecode.lib.hooks import log_pull_action
312 service = environ['QUERY_STRING'].split('=')
313 service = environ['QUERY_STRING'].split('=')
313
314
314 if len(service) < 2:
315 if len(service) < 2:
315 return
316 return
316
317
317 from rhodecode.model.db import Repository
318 from rhodecode.model.db import Repository
318 _repo = Repository.get_by_repo_name(repo_name)
319 _repo = Repository.get_by_repo_name(repo_name)
319 _repo = _repo.scm_instance
320 _repo = _repo.scm_instance
320
321
321 _hooks = dict(baseui.configitems('hooks')) or {}
322 _hooks = dict(baseui.configitems('hooks')) or {}
322 if action == 'pull':
323 if action == 'pull':
323 # stupid git, emulate pre-pull hook !
324 # stupid git, emulate pre-pull hook !
324 pre_pull(ui=baseui, repo=_repo._repo)
325 pre_pull(ui=baseui, repo=_repo._repo)
325 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
326 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
326 log_pull_action(ui=baseui, repo=_repo._repo)
327 log_pull_action(ui=baseui, repo=_repo._repo)
327
328
328 def __inject_extras(self, repo_path, baseui, extras={}):
329 def __inject_extras(self, repo_path, baseui, extras={}):
329 """
330 """
330 Injects some extra params into baseui instance
331 Injects some extra params into baseui instance
331
332
332 :param baseui: baseui instance
333 :param baseui: baseui instance
333 :param extras: dict with extra params to put into baseui
334 :param extras: dict with extra params to put into baseui
334 """
335 """
335
336
336 _set_extras(extras)
337 _set_extras(extras)
@@ -1,287 +1,288 b''
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 repo_path = os.path.join(safe_str(self.basepath), 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 log.debug('Repository path is %s' % repo_path)
173 log.debug('Repository path is %s' % repo_path)
173
174
174 # CHECK LOCKING only if it's not ANONYMOUS USER
175 # CHECK LOCKING only if it's not ANONYMOUS USER
175 if username != User.DEFAULT_USER:
176 if username != User.DEFAULT_USER:
176 log.debug('Checking locking on repository')
177 log.debug('Checking locking on repository')
177 (make_lock,
178 (make_lock,
178 locked,
179 locked,
179 locked_by) = self._check_locking_state(
180 locked_by) = self._check_locking_state(
180 environ=environ, action=action,
181 environ=environ, action=action,
181 repo=repo_name, user_id=user.user_id
182 repo=repo_name, user_id=user.user_id
182 )
183 )
183 # store the make_lock for later evaluation in hooks
184 # store the make_lock for later evaluation in hooks
184 extras.update({'make_lock': make_lock,
185 extras.update({'make_lock': make_lock,
185 'locked_by': locked_by})
186 'locked_by': locked_by})
186
187
187 # set the environ variables for this request
188 # set the environ variables for this request
188 os.environ['RC_SCM_DATA'] = json.dumps(extras)
189 os.environ['RC_SCM_DATA'] = json.dumps(extras)
189 fix_PATH()
190 fix_PATH()
190 log.debug('HOOKS extras is %s' % extras)
191 log.debug('HOOKS extras is %s' % extras)
191 baseui = make_ui('db')
192 baseui = make_ui('db')
192 self.__inject_extras(repo_path, baseui, extras)
193 self.__inject_extras(repo_path, baseui, extras)
193
194
194 try:
195 try:
195 log.info('%s action on HG repo "%s" by "%s" from %s' %
196 log.info('%s action on HG repo "%s" by "%s" from %s' %
196 (action, repo_name, username, ip_addr))
197 (action, str_repo_name, safe_str(username), ip_addr))
197 app = self.__make_app(repo_path, baseui, extras)
198 app = self.__make_app(repo_path, baseui, extras)
198 return app(environ, start_response)
199 return app(environ, start_response)
199 except RepoError, e:
200 except RepoError, e:
200 if str(e).find('not found') != -1:
201 if str(e).find('not found') != -1:
201 return HTTPNotFound()(environ, start_response)
202 return HTTPNotFound()(environ, start_response)
202 except HTTPLockedRC, e:
203 except HTTPLockedRC, e:
203 _code = CONFIG.get('lock_ret_code')
204 _code = CONFIG.get('lock_ret_code')
204 log.debug('Repository LOCKED ret code %s!' % (_code))
205 log.debug('Repository LOCKED ret code %s!' % (_code))
205 return e(environ, start_response)
206 return e(environ, start_response)
206 except Exception:
207 except Exception:
207 log.error(traceback.format_exc())
208 log.error(traceback.format_exc())
208 return HTTPInternalServerError()(environ, start_response)
209 return HTTPInternalServerError()(environ, start_response)
209 finally:
210 finally:
210 # invalidate cache on push
211 # invalidate cache on push
211 if action == 'push':
212 if action == 'push':
212 self._invalidate_cache(repo_name)
213 self._invalidate_cache(repo_name)
213
214
214 def __make_app(self, repo_name, baseui, extras):
215 def __make_app(self, repo_name, baseui, extras):
215 """
216 """
216 Make an wsgi application using hgweb, and inject generated baseui
217 Make an wsgi application using hgweb, and inject generated baseui
217 instance, additionally inject some extras into ui object
218 instance, additionally inject some extras into ui object
218 """
219 """
219 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
220 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
220
221
221 def __get_repository(self, environ):
222 def __get_repository(self, environ):
222 """
223 """
223 Get's repository name out of PATH_INFO header
224 Get's repository name out of PATH_INFO header
224
225
225 :param environ: environ where PATH_INFO is stored
226 :param environ: environ where PATH_INFO is stored
226 """
227 """
227 try:
228 try:
228 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
229 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
229 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
230 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
230 if repo_name.endswith('/'):
231 if repo_name.endswith('/'):
231 repo_name = repo_name.rstrip('/')
232 repo_name = repo_name.rstrip('/')
232 except Exception:
233 except Exception:
233 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
234 raise
235 raise
235
236
236 return repo_name
237 return repo_name
237
238
238 def __get_user(self, username):
239 def __get_user(self, username):
239 return User.get_by_username(username)
240 return User.get_by_username(username)
240
241
241 def __get_action(self, environ):
242 def __get_action(self, environ):
242 """
243 """
243 Maps mercurial request commands into a clone,pull or push command.
244 Maps mercurial request commands into a clone,pull or push command.
244 This should always return a valid command string
245 This should always return a valid command string
245
246
246 :param environ:
247 :param environ:
247 """
248 """
248 mapping = {'changegroup': 'pull',
249 mapping = {'changegroup': 'pull',
249 'changegroupsubset': 'pull',
250 'changegroupsubset': 'pull',
250 'stream_out': 'pull',
251 'stream_out': 'pull',
251 'listkeys': 'pull',
252 'listkeys': 'pull',
252 'unbundle': 'push',
253 'unbundle': 'push',
253 'pushkey': 'push', }
254 'pushkey': 'push', }
254 for qry in environ['QUERY_STRING'].split('&'):
255 for qry in environ['QUERY_STRING'].split('&'):
255 if qry.startswith('cmd'):
256 if qry.startswith('cmd'):
256 cmd = qry.split('=')[-1]
257 cmd = qry.split('=')[-1]
257 if cmd in mapping:
258 if cmd in mapping:
258 return mapping[cmd]
259 return mapping[cmd]
259
260
260 return 'pull'
261 return 'pull'
261
262
262 raise Exception('Unable to detect pull/push action !!'
263 raise Exception('Unable to detect pull/push action !!'
263 'Are you using non standard command or client ?')
264 'Are you using non standard command or client ?')
264
265
265 def __inject_extras(self, repo_path, baseui, extras={}):
266 def __inject_extras(self, repo_path, baseui, extras={}):
266 """
267 """
267 Injects some extra params into baseui instance
268 Injects some extra params into baseui instance
268
269
269 also overwrites global settings with those takes from local hgrc file
270 also overwrites global settings with those takes from local hgrc file
270
271
271 :param baseui: baseui instance
272 :param baseui: baseui instance
272 :param extras: dict with extra params to put into baseui
273 :param extras: dict with extra params to put into baseui
273 """
274 """
274
275
275 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
276 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
276
277
277 # make our hgweb quiet so it doesn't print output
278 # make our hgweb quiet so it doesn't print output
278 baseui.setconfig('ui', 'quiet', 'true')
279 baseui.setconfig('ui', 'quiet', 'true')
279
280
280 repoui = make_ui('file', hgrc, False)
281 repoui = make_ui('file', hgrc, False)
281
282
282 if repoui:
283 if repoui:
283 #overwrite our ui instance with the section from hgrc file
284 #overwrite our ui instance with the section from hgrc file
284 for section in ui_sections:
285 for section in ui_sections:
285 for k, v in repoui.configitems(section):
286 for k, v in repoui.configitems(section):
286 baseui.setconfig(section, k, v)
287 baseui.setconfig(section, k, v)
287 _set_extras(extras)
288 _set_extras(extras)
General Comments 0
You need to be logged in to leave comments. Login now