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