##// END OF EJS Templates
backported fixes for issue #318...
marcink -
r1834:d0e477b5 default
parent child Browse files
Show More
@@ -1,291 +1,302
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) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 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 import time
30
31
31 from dulwich import server as dulserver
32 from dulwich import server as dulserver
32
33
33
34
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35
36
36 def handle(self):
37 def handle(self):
37 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
38
39
39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 self.repo.object_store,
41 self.repo.object_store,
41 self.repo.get_peeled)
42 self.repo.get_peeled)
42 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
43 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
44 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
45
46
46 # Do they want any objects?
47 # Do they want any objects?
47 if objects_iter is None or len(objects_iter) == 0:
48 if objects_iter is None or len(objects_iter) == 0:
48 return
49 return
49
50
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 objects_iter, len(objects_iter))
53 objects_iter, len(objects_iter))
53 messages = []
54 messages = []
54 messages.append('thank you for using rhodecode')
55 messages.append('thank you for using rhodecode')
55
56
56 for msg in messages:
57 for msg in messages:
57 self.progress(msg + "\n")
58 self.progress(msg + "\n")
58 # we are done
59 # we are done
59 self.proto.write("0000")
60 self.proto.write("0000")
60
61
61 dulserver.DEFAULT_HANDLERS = {
62 dulserver.DEFAULT_HANDLERS = {
62 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
64 }
65 }
65
66
66 from dulwich.repo import Repo
67 from dulwich.repo import Repo
67 from dulwich.web import HTTPGitApplication
68 from dulwich.web import HTTPGitApplication
68
69
69 from paste.auth.basic import AuthBasicAuthenticator
70 from paste.auth.basic import AuthBasicAuthenticator
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
72
72 from rhodecode.lib import safe_str
73 from rhodecode.lib import safe_str
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
75 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
76 from rhodecode.model import meta
75 from rhodecode.model.db import User
77 from rhodecode.model.db import User
76
78
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
79 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78
80
79 log = logging.getLogger(__name__)
81 log = logging.getLogger(__name__)
80
82
81
83
82 def is_git(environ):
84 def is_git(environ):
83 """Returns True if request's target is git server.
85 """Returns True if request's target is git server.
84 ``HTTP_USER_AGENT`` would then have git client version given.
86 ``HTTP_USER_AGENT`` would then have git client version given.
85
87
86 :param environ:
88 :param environ:
87 """
89 """
88 http_user_agent = environ.get('HTTP_USER_AGENT')
90 http_user_agent = environ.get('HTTP_USER_AGENT')
89 if http_user_agent and http_user_agent.startswith('git'):
91 if http_user_agent and http_user_agent.startswith('git'):
90 return True
92 return True
91 return False
93 return False
92
94
93
95
94 class SimpleGit(object):
96 class SimpleGit(object):
95
97
96 def __init__(self, application, config):
98 def __init__(self, application, config):
97 self.application = application
99 self.application = application
98 self.config = config
100 self.config = config
99 # base path of repo locations
101 # base path of repo locations
100 self.basepath = self.config['base_path']
102 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
103 #authenticate this mercurial request using authfunc
102 self.authenticate = AuthBasicAuthenticator('', authfunc)
104 self.authenticate = AuthBasicAuthenticator('', authfunc)
103
105
104 def __call__(self, environ, start_response):
106 def __call__(self, environ, start_response):
107 start = time.time()
108 try:
109 return self._handle_request(environ, start_response)
110 finally:
111 log = logging.getLogger(self.__class__.__name__)
112 log.debug('Request time: %.3fs' % (time.time() - start))
113 meta.Session.remove()
114
115 def _handle_request(self, environ, start_response):
105 if not is_git(environ):
116 if not is_git(environ):
106 return self.application(environ, start_response)
117 return self.application(environ, start_response)
107
118
108 proxy_key = 'HTTP_X_REAL_IP'
119 proxy_key = 'HTTP_X_REAL_IP'
109 def_key = 'REMOTE_ADDR'
120 def_key = 'REMOTE_ADDR'
110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
121 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
111 username = None
122 username = None
112 # skip passing error to error controller
123 # skip passing error to error controller
113 environ['pylons.status_code_redirect'] = True
124 environ['pylons.status_code_redirect'] = True
114
125
115 #======================================================================
126 #======================================================================
116 # EXTRACT REPOSITORY NAME FROM ENV
127 # EXTRACT REPOSITORY NAME FROM ENV
117 #======================================================================
128 #======================================================================
118 try:
129 try:
119 repo_name = self.__get_repository(environ)
130 repo_name = self.__get_repository(environ)
120 log.debug('Extracted repo name is %s' % repo_name)
131 log.debug('Extracted repo name is %s' % repo_name)
121 except:
132 except:
122 return HTTPInternalServerError()(environ, start_response)
133 return HTTPInternalServerError()(environ, start_response)
123
134
124 #======================================================================
135 #======================================================================
125 # GET ACTION PULL or PUSH
136 # GET ACTION PULL or PUSH
126 #======================================================================
137 #======================================================================
127 action = self.__get_action(environ)
138 action = self.__get_action(environ)
128
139
129 #======================================================================
140 #======================================================================
130 # CHECK ANONYMOUS PERMISSION
141 # CHECK ANONYMOUS PERMISSION
131 #======================================================================
142 #======================================================================
132 if action in ['pull', 'push']:
143 if action in ['pull', 'push']:
133 anonymous_user = self.__get_user('default')
144 anonymous_user = self.__get_user('default')
134 username = anonymous_user.username
145 username = anonymous_user.username
135 anonymous_perm = self.__check_permission(action,
146 anonymous_perm = self.__check_permission(action,
136 anonymous_user,
147 anonymous_user,
137 repo_name)
148 repo_name)
138
149
139 if anonymous_perm is not True or anonymous_user.active is False:
150 if anonymous_perm is not True or anonymous_user.active is False:
140 if anonymous_perm is not True:
151 if anonymous_perm is not True:
141 log.debug('Not enough credentials to access this '
152 log.debug('Not enough credentials to access this '
142 'repository as anonymous user')
153 'repository as anonymous user')
143 if anonymous_user.active is False:
154 if anonymous_user.active is False:
144 log.debug('Anonymous access is disabled, running '
155 log.debug('Anonymous access is disabled, running '
145 'authentication')
156 'authentication')
146 #==============================================================
157 #==============================================================
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
158 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
159 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 #==============================================================
160 #==============================================================
150
161
151 if not REMOTE_USER(environ):
162 if not REMOTE_USER(environ):
152 self.authenticate.realm = \
163 self.authenticate.realm = \
153 safe_str(self.config['rhodecode_realm'])
164 safe_str(self.config['rhodecode_realm'])
154 result = self.authenticate(environ)
165 result = self.authenticate(environ)
155 if isinstance(result, str):
166 if isinstance(result, str):
156 AUTH_TYPE.update(environ, 'basic')
167 AUTH_TYPE.update(environ, 'basic')
157 REMOTE_USER.update(environ, result)
168 REMOTE_USER.update(environ, result)
158 else:
169 else:
159 return result.wsgi_application(environ, start_response)
170 return result.wsgi_application(environ, start_response)
160
171
161 #==============================================================
172 #==============================================================
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
163 # BASIC AUTH
174 # BASIC AUTH
164 #==============================================================
175 #==============================================================
165
176
166 if action in ['pull', 'push']:
177 if action in ['pull', 'push']:
167 username = REMOTE_USER(environ)
178 username = REMOTE_USER(environ)
168 try:
179 try:
169 user = self.__get_user(username)
180 user = self.__get_user(username)
170 if user is None:
181 if user is None:
171 return HTTPForbidden()(environ, start_response)
182 return HTTPForbidden()(environ, start_response)
172 username = user.username
183 username = user.username
173 except:
184 except:
174 log.error(traceback.format_exc())
185 log.error(traceback.format_exc())
175 return HTTPInternalServerError()(environ,
186 return HTTPInternalServerError()(environ,
176 start_response)
187 start_response)
177
188
178 #check permissions for this repository
189 #check permissions for this repository
179 perm = self.__check_permission(action, user,
190 perm = self.__check_permission(action, user,
180 repo_name)
191 repo_name)
181 if perm is not True:
192 if perm is not True:
182 return HTTPForbidden()(environ, start_response)
193 return HTTPForbidden()(environ, start_response)
183
194
184 extras = {'ip': ipaddr,
195 extras = {'ip': ipaddr,
185 'username': username,
196 'username': username,
186 'action': action,
197 'action': action,
187 'repository': repo_name}
198 'repository': repo_name}
188
199
189 #===================================================================
200 #===================================================================
190 # GIT REQUEST HANDLING
201 # GIT REQUEST HANDLING
191 #===================================================================
202 #===================================================================
192
203
193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
204 repo_path = safe_str(os.path.join(self.basepath, repo_name))
194 log.debug('Repository path is %s' % repo_path)
205 log.debug('Repository path is %s' % repo_path)
195
206
196 # quick check if that dir exists...
207 # quick check if that dir exists...
197 if is_valid_repo(repo_name, self.basepath) is False:
208 if is_valid_repo(repo_name, self.basepath) is False:
198 return HTTPNotFound()(environ, start_response)
209 return HTTPNotFound()(environ, start_response)
199
210
200 try:
211 try:
201 #invalidate cache on push
212 #invalidate cache on push
202 if action == 'push':
213 if action == 'push':
203 self.__invalidate_cache(repo_name)
214 self.__invalidate_cache(repo_name)
204
215
205 app = self.__make_app(repo_name, repo_path)
216 app = self.__make_app(repo_name, repo_path)
206 return app(environ, start_response)
217 return app(environ, start_response)
207 except Exception:
218 except Exception:
208 log.error(traceback.format_exc())
219 log.error(traceback.format_exc())
209 return HTTPInternalServerError()(environ, start_response)
220 return HTTPInternalServerError()(environ, start_response)
210
221
211 def __make_app(self, repo_name, repo_path):
222 def __make_app(self, repo_name, repo_path):
212 """
223 """
213 Make an wsgi application using dulserver
224 Make an wsgi application using dulserver
214
225
215 :param repo_name: name of the repository
226 :param repo_name: name of the repository
216 :param repo_path: full path to the repository
227 :param repo_path: full path to the repository
217 """
228 """
218
229
219 _d = {'/' + repo_name: Repo(repo_path)}
230 _d = {'/' + repo_name: Repo(repo_path)}
220 backend = dulserver.DictBackend(_d)
231 backend = dulserver.DictBackend(_d)
221 gitserve = HTTPGitApplication(backend)
232 gitserve = HTTPGitApplication(backend)
222
233
223 return gitserve
234 return gitserve
224
235
225 def __check_permission(self, action, user, repo_name):
236 def __check_permission(self, action, user, repo_name):
226 """
237 """
227 Checks permissions using action (push/pull) user and repository
238 Checks permissions using action (push/pull) user and repository
228 name
239 name
229
240
230 :param action: push or pull action
241 :param action: push or pull action
231 :param user: user instance
242 :param user: user instance
232 :param repo_name: repository name
243 :param repo_name: repository name
233 """
244 """
234 if action == 'push':
245 if action == 'push':
235 if not HasPermissionAnyMiddleware('repository.write',
246 if not HasPermissionAnyMiddleware('repository.write',
236 'repository.admin')(user,
247 'repository.admin')(user,
237 repo_name):
248 repo_name):
238 return False
249 return False
239
250
240 else:
251 else:
241 #any other action need at least read permission
252 #any other action need at least read permission
242 if not HasPermissionAnyMiddleware('repository.read',
253 if not HasPermissionAnyMiddleware('repository.read',
243 'repository.write',
254 'repository.write',
244 'repository.admin')(user,
255 'repository.admin')(user,
245 repo_name):
256 repo_name):
246 return False
257 return False
247
258
248 return True
259 return True
249
260
250 def __get_repository(self, environ):
261 def __get_repository(self, environ):
251 """
262 """
252 Get's repository name out of PATH_INFO header
263 Get's repository name out of PATH_INFO header
253
264
254 :param environ: environ where PATH_INFO is stored
265 :param environ: environ where PATH_INFO is stored
255 """
266 """
256 try:
267 try:
257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
268 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
258 if repo_name.endswith('/'):
269 if repo_name.endswith('/'):
259 repo_name = repo_name.rstrip('/')
270 repo_name = repo_name.rstrip('/')
260 except:
271 except:
261 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
262 raise
273 raise
263 repo_name = repo_name.split('/')[0]
274 repo_name = repo_name.split('/')[0]
264 return repo_name
275 return repo_name
265
276
266 def __get_user(self, username):
277 def __get_user(self, username):
267 return User.get_by_username(username)
278 return User.get_by_username(username)
268
279
269 def __get_action(self, environ):
280 def __get_action(self, environ):
270 """Maps git request commands into a pull or push command.
281 """Maps git request commands into a pull or push command.
271
282
272 :param environ:
283 :param environ:
273 """
284 """
274 service = environ['QUERY_STRING'].split('=')
285 service = environ['QUERY_STRING'].split('=')
275 if len(service) > 1:
286 if len(service) > 1:
276 service_cmd = service[1]
287 service_cmd = service[1]
277 mapping = {'git-receive-pack': 'push',
288 mapping = {'git-receive-pack': 'push',
278 'git-upload-pack': 'pull',
289 'git-upload-pack': 'pull',
279 }
290 }
280
291
281 return mapping.get(service_cmd,
292 return mapping.get(service_cmd,
282 service_cmd if service_cmd else 'other')
293 service_cmd if service_cmd else 'other')
283 else:
294 else:
284 return 'other'
295 return 'other'
285
296
286 def __invalidate_cache(self, repo_name):
297 def __invalidate_cache(self, repo_name):
287 """we know that some change was made to repositories and we should
298 """we know that some change was made to repositories and we should
288 invalidate the cache to see the changes right away but only for
299 invalidate the cache to see the changes right away but only for
289 push requests"""
300 push requests"""
290 invalidate_cache('get_repo_cached_%s' % repo_name)
301 invalidate_cache('get_repo_cached_%s' % repo_name)
291
302
@@ -1,290 +1,301
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) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 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 import time
30
31
31 from mercurial.error import RepoError
32 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
33
34
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
37
37 from rhodecode.lib import safe_str
38 from rhodecode.lib import safe_str
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 is_valid_repo, ui_sections
41 is_valid_repo, ui_sections
42 from rhodecode.model import meta
41 from rhodecode.model.db import User
43 from rhodecode.model.db import User
42
44
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
45 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
46
45 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
46
48
47
49
48 def is_mercurial(environ):
50 def is_mercurial(environ):
49 """Returns True if request's target is mercurial server - header
51 """Returns True if request's target is mercurial server - header
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 """
53 """
52 http_accept = environ.get('HTTP_ACCEPT')
54 http_accept = environ.get('HTTP_ACCEPT')
53 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
54 return True
56 return True
55 return False
57 return False
56
58
57
59
58 class SimpleHg(object):
60 class SimpleHg(object):
59
61
60 def __init__(self, application, config):
62 def __init__(self, application, config):
61 self.application = application
63 self.application = application
62 self.config = config
64 self.config = config
63 # base path of repo locations
65 # base path of repo locations
64 self.basepath = self.config['base_path']
66 self.basepath = self.config['base_path']
65 #authenticate this mercurial request using authfunc
67 #authenticate this mercurial request using authfunc
66 self.authenticate = AuthBasicAuthenticator('', authfunc)
68 self.authenticate = AuthBasicAuthenticator('', authfunc)
67 self.ipaddr = '0.0.0.0'
69 self.ipaddr = '0.0.0.0'
68
70
69 def __call__(self, environ, start_response):
71 def __call__(self, environ, start_response):
72 start = time.time()
73 try:
74 return self._handle_request(environ, start_response)
75 finally:
76 log = logging.getLogger(self.__class__.__name__)
77 log.debug('Request time: %.3fs' % (time.time() - start))
78 meta.Session.remove()
79
80 def _handle_request(self, environ, start_response):
70 if not is_mercurial(environ):
81 if not is_mercurial(environ):
71 return self.application(environ, start_response)
82 return self.application(environ, start_response)
72
83
73 proxy_key = 'HTTP_X_REAL_IP'
84 proxy_key = 'HTTP_X_REAL_IP'
74 def_key = 'REMOTE_ADDR'
85 def_key = 'REMOTE_ADDR'
75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
86 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76
87
77 # skip passing error to error controller
88 # skip passing error to error controller
78 environ['pylons.status_code_redirect'] = True
89 environ['pylons.status_code_redirect'] = True
79
90
80 #======================================================================
91 #======================================================================
81 # EXTRACT REPOSITORY NAME FROM ENV
92 # EXTRACT REPOSITORY NAME FROM ENV
82 #======================================================================
93 #======================================================================
83 try:
94 try:
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
95 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 log.debug('Extracted repo name is %s' % repo_name)
96 log.debug('Extracted repo name is %s' % repo_name)
86 except:
97 except:
87 return HTTPInternalServerError()(environ, start_response)
98 return HTTPInternalServerError()(environ, start_response)
88
99
89 #======================================================================
100 #======================================================================
90 # GET ACTION PULL or PUSH
101 # GET ACTION PULL or PUSH
91 #======================================================================
102 #======================================================================
92 action = self.__get_action(environ)
103 action = self.__get_action(environ)
93
104
94 #======================================================================
105 #======================================================================
95 # CHECK ANONYMOUS PERMISSION
106 # CHECK ANONYMOUS PERMISSION
96 #======================================================================
107 #======================================================================
97 if action in ['pull', 'push']:
108 if action in ['pull', 'push']:
98 anonymous_user = self.__get_user('default')
109 anonymous_user = self.__get_user('default')
99
110
100 username = anonymous_user.username
111 username = anonymous_user.username
101 anonymous_perm = self.__check_permission(action,
112 anonymous_perm = self.__check_permission(action,
102 anonymous_user,
113 anonymous_user,
103 repo_name)
114 repo_name)
104
115
105 if anonymous_perm is not True or anonymous_user.active is False:
116 if anonymous_perm is not True or anonymous_user.active is False:
106 if anonymous_perm is not True:
117 if anonymous_perm is not True:
107 log.debug('Not enough credentials to access this '
118 log.debug('Not enough credentials to access this '
108 'repository as anonymous user')
119 'repository as anonymous user')
109 if anonymous_user.active is False:
120 if anonymous_user.active is False:
110 log.debug('Anonymous access is disabled, running '
121 log.debug('Anonymous access is disabled, running '
111 'authentication')
122 'authentication')
112 #==============================================================
123 #==============================================================
113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
124 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
125 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
115 #==============================================================
126 #==============================================================
116
127
117 if not REMOTE_USER(environ):
128 if not REMOTE_USER(environ):
118 self.authenticate.realm = \
129 self.authenticate.realm = \
119 safe_str(self.config['rhodecode_realm'])
130 safe_str(self.config['rhodecode_realm'])
120 result = self.authenticate(environ)
131 result = self.authenticate(environ)
121 if isinstance(result, str):
132 if isinstance(result, str):
122 AUTH_TYPE.update(environ, 'basic')
133 AUTH_TYPE.update(environ, 'basic')
123 REMOTE_USER.update(environ, result)
134 REMOTE_USER.update(environ, result)
124 else:
135 else:
125 return result.wsgi_application(environ, start_response)
136 return result.wsgi_application(environ, start_response)
126
137
127 #==============================================================
138 #==============================================================
128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
139 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
129 # BASIC AUTH
140 # BASIC AUTH
130 #==============================================================
141 #==============================================================
131
142
132 if action in ['pull', 'push']:
143 if action in ['pull', 'push']:
133 username = REMOTE_USER(environ)
144 username = REMOTE_USER(environ)
134 try:
145 try:
135 user = self.__get_user(username)
146 user = self.__get_user(username)
136 if user is None:
147 if user is None:
137 return HTTPForbidden()(environ, start_response)
148 return HTTPForbidden()(environ, start_response)
138 username = user.username
149 username = user.username
139 except:
150 except:
140 log.error(traceback.format_exc())
151 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ,
152 return HTTPInternalServerError()(environ,
142 start_response)
153 start_response)
143
154
144 #check permissions for this repository
155 #check permissions for this repository
145 perm = self.__check_permission(action, user,
156 perm = self.__check_permission(action, user,
146 repo_name)
157 repo_name)
147 if perm is not True:
158 if perm is not True:
148 return HTTPForbidden()(environ, start_response)
159 return HTTPForbidden()(environ, start_response)
149
160
150 extras = {'ip': ipaddr,
161 extras = {'ip': ipaddr,
151 'username': username,
162 'username': username,
152 'action': action,
163 'action': action,
153 'repository': repo_name}
164 'repository': repo_name}
154
165
155 #======================================================================
166 #======================================================================
156 # MERCURIAL REQUEST HANDLING
167 # MERCURIAL REQUEST HANDLING
157 #======================================================================
168 #======================================================================
158
169
159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
170 repo_path = safe_str(os.path.join(self.basepath, repo_name))
160 log.debug('Repository path is %s' % repo_path)
171 log.debug('Repository path is %s' % repo_path)
161
172
162 baseui = make_ui('db')
173 baseui = make_ui('db')
163 self.__inject_extras(repo_path, baseui, extras)
174 self.__inject_extras(repo_path, baseui, extras)
164
175
165
176
166 # quick check if that dir exists...
177 # quick check if that dir exists...
167 if is_valid_repo(repo_name, self.basepath) is False:
178 if is_valid_repo(repo_name, self.basepath) is False:
168 return HTTPNotFound()(environ, start_response)
179 return HTTPNotFound()(environ, start_response)
169
180
170 try:
181 try:
171 #invalidate cache on push
182 #invalidate cache on push
172 if action == 'push':
183 if action == 'push':
173 self.__invalidate_cache(repo_name)
184 self.__invalidate_cache(repo_name)
174
185
175 app = self.__make_app(repo_path, baseui, extras)
186 app = self.__make_app(repo_path, baseui, extras)
176 return app(environ, start_response)
187 return app(environ, start_response)
177 except RepoError, e:
188 except RepoError, e:
178 if str(e).find('not found') != -1:
189 if str(e).find('not found') != -1:
179 return HTTPNotFound()(environ, start_response)
190 return HTTPNotFound()(environ, start_response)
180 except Exception:
191 except Exception:
181 log.error(traceback.format_exc())
192 log.error(traceback.format_exc())
182 return HTTPInternalServerError()(environ, start_response)
193 return HTTPInternalServerError()(environ, start_response)
183
194
184 def __make_app(self, repo_name, baseui, extras):
195 def __make_app(self, repo_name, baseui, extras):
185 """
196 """
186 Make an wsgi application using hgweb, and inject generated baseui
197 Make an wsgi application using hgweb, and inject generated baseui
187 instance, additionally inject some extras into ui object
198 instance, additionally inject some extras into ui object
188 """
199 """
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
200 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
190
201
191
202
192 def __check_permission(self, action, user, repo_name):
203 def __check_permission(self, action, user, repo_name):
193 """
204 """
194 Checks permissions using action (push/pull) user and repository
205 Checks permissions using action (push/pull) user and repository
195 name
206 name
196
207
197 :param action: push or pull action
208 :param action: push or pull action
198 :param user: user instance
209 :param user: user instance
199 :param repo_name: repository name
210 :param repo_name: repository name
200 """
211 """
201 if action == 'push':
212 if action == 'push':
202 if not HasPermissionAnyMiddleware('repository.write',
213 if not HasPermissionAnyMiddleware('repository.write',
203 'repository.admin')(user,
214 'repository.admin')(user,
204 repo_name):
215 repo_name):
205 return False
216 return False
206
217
207 else:
218 else:
208 #any other action need at least read permission
219 #any other action need at least read permission
209 if not HasPermissionAnyMiddleware('repository.read',
220 if not HasPermissionAnyMiddleware('repository.read',
210 'repository.write',
221 'repository.write',
211 'repository.admin')(user,
222 'repository.admin')(user,
212 repo_name):
223 repo_name):
213 return False
224 return False
214
225
215 return True
226 return True
216
227
217 def __get_repository(self, environ):
228 def __get_repository(self, environ):
218 """
229 """
219 Get's repository name out of PATH_INFO header
230 Get's repository name out of PATH_INFO header
220
231
221 :param environ: environ where PATH_INFO is stored
232 :param environ: environ where PATH_INFO is stored
222 """
233 """
223 try:
234 try:
224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
235 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
225 if repo_name.endswith('/'):
236 if repo_name.endswith('/'):
226 repo_name = repo_name.rstrip('/')
237 repo_name = repo_name.rstrip('/')
227 except:
238 except:
228 log.error(traceback.format_exc())
239 log.error(traceback.format_exc())
229 raise
240 raise
230
241
231 return repo_name
242 return repo_name
232
243
233 def __get_user(self, username):
244 def __get_user(self, username):
234 return User.get_by_username(username)
245 return User.get_by_username(username)
235
246
236 def __get_action(self, environ):
247 def __get_action(self, environ):
237 """
248 """
238 Maps mercurial request commands into a clone,pull or push command.
249 Maps mercurial request commands into a clone,pull or push command.
239 This should always return a valid command string
250 This should always return a valid command string
240
251
241 :param environ:
252 :param environ:
242 """
253 """
243 mapping = {'changegroup': 'pull',
254 mapping = {'changegroup': 'pull',
244 'changegroupsubset': 'pull',
255 'changegroupsubset': 'pull',
245 'stream_out': 'pull',
256 'stream_out': 'pull',
246 'listkeys': 'pull',
257 'listkeys': 'pull',
247 'unbundle': 'push',
258 'unbundle': 'push',
248 'pushkey': 'push', }
259 'pushkey': 'push', }
249 for qry in environ['QUERY_STRING'].split('&'):
260 for qry in environ['QUERY_STRING'].split('&'):
250 if qry.startswith('cmd'):
261 if qry.startswith('cmd'):
251 cmd = qry.split('=')[-1]
262 cmd = qry.split('=')[-1]
252 if cmd in mapping:
263 if cmd in mapping:
253 return mapping[cmd]
264 return mapping[cmd]
254 else:
265 else:
255 return 'pull'
266 return 'pull'
256
267
257 def __invalidate_cache(self, repo_name):
268 def __invalidate_cache(self, repo_name):
258 """we know that some change was made to repositories and we should
269 """we know that some change was made to repositories and we should
259 invalidate the cache to see the changes right away but only for
270 invalidate the cache to see the changes right away but only for
260 push requests"""
271 push requests"""
261 invalidate_cache('get_repo_cached_%s' % repo_name)
272 invalidate_cache('get_repo_cached_%s' % repo_name)
262
273
263 def __inject_extras(self, repo_path, baseui, extras={}):
274 def __inject_extras(self, repo_path, baseui, extras={}):
264 """
275 """
265 Injects some extra params into baseui instance
276 Injects some extra params into baseui instance
266
277
267 also overwrites global settings with those takes from local hgrc file
278 also overwrites global settings with those takes from local hgrc file
268
279
269 :param baseui: baseui instance
280 :param baseui: baseui instance
270 :param extras: dict with extra params to put into baseui
281 :param extras: dict with extra params to put into baseui
271 """
282 """
272
283
273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
284 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
274
285
275 # make our hgweb quiet so it doesn't print output
286 # make our hgweb quiet so it doesn't print output
276 baseui.setconfig('ui', 'quiet', 'true')
287 baseui.setconfig('ui', 'quiet', 'true')
277
288
278 #inject some additional parameters that will be available in ui
289 #inject some additional parameters that will be available in ui
279 #for hooks
290 #for hooks
280 for k, v in extras.items():
291 for k, v in extras.items():
281 baseui.setconfig('rhodecode_extras', k, v)
292 baseui.setconfig('rhodecode_extras', k, v)
282
293
283 repoui = make_ui('file', hgrc, False)
294 repoui = make_ui('file', hgrc, False)
284
295
285 if repoui:
296 if repoui:
286 #overwrite our ui instance with the section from hgrc file
297 #overwrite our ui instance with the section from hgrc file
287 for section in ui_sections:
298 for section in ui_sections:
288 for k, v in repoui.configitems(section):
299 for k, v in repoui.configitems(section):
289 baseui.setconfig(section, k, v)
300 baseui.setconfig(section, k, v)
290
301
General Comments 0
You need to be logged in to leave comments. Login now