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