##// END OF EJS Templates
Fixed middleware to prevent deactivated users from authenticating
Liad Shani -
r1620:41696fc7 beta
parent child Browse files
Show More
@@ -1,291 +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, get_container_username
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware, get_container_username
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 get_container_username(environ, self.config):
151 if not get_container_username(environ, self.config):
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 = get_container_username(environ, self.config)
167 username = get_container_username(environ, self.config)
168 try:
168 try:
169 user = self.__get_user(username)
169 user = self.__get_user(username)
170 if user is None:
170 if user is None or not user.active:
171 return HTTPForbidden()(environ, start_response)
171 return HTTPForbidden()(environ, start_response)
172 username = user.username
172 username = user.username
173 except:
173 except:
174 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
175 return HTTPInternalServerError()(environ,
175 return HTTPInternalServerError()(environ,
176 start_response)
176 start_response)
177
177
178 #check permissions for this repository
178 #check permissions for this repository
179 perm = self.__check_permission(action, user,
179 perm = self.__check_permission(action, user,
180 repo_name)
180 repo_name)
181 if perm is not True:
181 if perm is not True:
182 return HTTPForbidden()(environ, start_response)
182 return HTTPForbidden()(environ, start_response)
183
183
184 extras = {'ip': ipaddr,
184 extras = {'ip': ipaddr,
185 'username': username,
185 'username': username,
186 'action': action,
186 'action': action,
187 'repository': repo_name}
187 'repository': repo_name}
188
188
189 #===================================================================
189 #===================================================================
190 # GIT REQUEST HANDLING
190 # GIT REQUEST HANDLING
191 #===================================================================
191 #===================================================================
192
192
193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
194 log.debug('Repository path is %s' % repo_path)
194 log.debug('Repository path is %s' % repo_path)
195
195
196 # quick check if that dir exists...
196 # quick check if that dir exists...
197 if is_valid_repo(repo_name, self.basepath) is False:
197 if is_valid_repo(repo_name, self.basepath) is False:
198 return HTTPNotFound()(environ, start_response)
198 return HTTPNotFound()(environ, start_response)
199
199
200 try:
200 try:
201 #invalidate cache on push
201 #invalidate cache on push
202 if action == 'push':
202 if action == 'push':
203 self.__invalidate_cache(repo_name)
203 self.__invalidate_cache(repo_name)
204
204
205 app = self.__make_app(repo_name, repo_path)
205 app = self.__make_app(repo_name, repo_path)
206 return app(environ, start_response)
206 return app(environ, start_response)
207 except Exception:
207 except Exception:
208 log.error(traceback.format_exc())
208 log.error(traceback.format_exc())
209 return HTTPInternalServerError()(environ, start_response)
209 return HTTPInternalServerError()(environ, start_response)
210
210
211 def __make_app(self, repo_name, repo_path):
211 def __make_app(self, repo_name, repo_path):
212 """
212 """
213 Make an wsgi application using dulserver
213 Make an wsgi application using dulserver
214
214
215 :param repo_name: name of the repository
215 :param repo_name: name of the repository
216 :param repo_path: full path to the repository
216 :param repo_path: full path to the repository
217 """
217 """
218
218
219 _d = {'/' + repo_name: Repo(repo_path)}
219 _d = {'/' + repo_name: Repo(repo_path)}
220 backend = dulserver.DictBackend(_d)
220 backend = dulserver.DictBackend(_d)
221 gitserve = HTTPGitApplication(backend)
221 gitserve = HTTPGitApplication(backend)
222
222
223 return gitserve
223 return gitserve
224
224
225 def __check_permission(self, action, user, repo_name):
225 def __check_permission(self, action, user, repo_name):
226 """
226 """
227 Checks permissions using action (push/pull) user and repository
227 Checks permissions using action (push/pull) user and repository
228 name
228 name
229
229
230 :param action: push or pull action
230 :param action: push or pull action
231 :param user: user instance
231 :param user: user instance
232 :param repo_name: repository name
232 :param repo_name: repository name
233 """
233 """
234 if action == 'push':
234 if action == 'push':
235 if not HasPermissionAnyMiddleware('repository.write',
235 if not HasPermissionAnyMiddleware('repository.write',
236 'repository.admin')(user,
236 'repository.admin')(user,
237 repo_name):
237 repo_name):
238 return False
238 return False
239
239
240 else:
240 else:
241 #any other action need at least read permission
241 #any other action need at least read permission
242 if not HasPermissionAnyMiddleware('repository.read',
242 if not HasPermissionAnyMiddleware('repository.read',
243 'repository.write',
243 'repository.write',
244 'repository.admin')(user,
244 'repository.admin')(user,
245 repo_name):
245 repo_name):
246 return False
246 return False
247
247
248 return True
248 return True
249
249
250 def __get_repository(self, environ):
250 def __get_repository(self, environ):
251 """
251 """
252 Get's repository name out of PATH_INFO header
252 Get's repository name out of PATH_INFO header
253
253
254 :param environ: environ where PATH_INFO is stored
254 :param environ: environ where PATH_INFO is stored
255 """
255 """
256 try:
256 try:
257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
258 if repo_name.endswith('/'):
258 if repo_name.endswith('/'):
259 repo_name = repo_name.rstrip('/')
259 repo_name = repo_name.rstrip('/')
260 except:
260 except:
261 log.error(traceback.format_exc())
261 log.error(traceback.format_exc())
262 raise
262 raise
263 repo_name = repo_name.split('/')[0]
263 repo_name = repo_name.split('/')[0]
264 return repo_name
264 return repo_name
265
265
266 def __get_user(self, username):
266 def __get_user(self, username):
267 return User.get_by_username(username)
267 return User.get_by_username(username)
268
268
269 def __get_action(self, environ):
269 def __get_action(self, environ):
270 """Maps git request commands into a pull or push command.
270 """Maps git request commands into a pull or push command.
271
271
272 :param environ:
272 :param environ:
273 """
273 """
274 service = environ['QUERY_STRING'].split('=')
274 service = environ['QUERY_STRING'].split('=')
275 if len(service) > 1:
275 if len(service) > 1:
276 service_cmd = service[1]
276 service_cmd = service[1]
277 mapping = {'git-receive-pack': 'push',
277 mapping = {'git-receive-pack': 'push',
278 'git-upload-pack': 'pull',
278 'git-upload-pack': 'pull',
279 }
279 }
280
280
281 return mapping.get(service_cmd,
281 return mapping.get(service_cmd,
282 service_cmd if service_cmd else 'other')
282 service_cmd if service_cmd else 'other')
283 else:
283 else:
284 return 'other'
284 return 'other'
285
285
286 def __invalidate_cache(self, repo_name):
286 def __invalidate_cache(self, repo_name):
287 """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
288 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
289 push requests"""
289 push requests"""
290 invalidate_cache('get_repo_cached_%s' % repo_name)
290 invalidate_cache('get_repo_cached_%s' % repo_name)
291
291
@@ -1,290 +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, get_container_username
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware, get_container_username
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 get_container_username(environ, self.config):
117 if not get_container_username(environ, self.config):
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 = get_container_username(environ, self.config)
133 username = get_container_username(environ, self.config)
134 try:
134 try:
135 user = self.__get_user(username)
135 user = self.__get_user(username)
136 if user is None:
136 if user is None or not user.active:
137 return HTTPForbidden()(environ, start_response)
137 return HTTPForbidden()(environ, start_response)
138 username = user.username
138 username = user.username
139 except:
139 except:
140 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ,
141 return HTTPInternalServerError()(environ,
142 start_response)
142 start_response)
143
143
144 #check permissions for this repository
144 #check permissions for this repository
145 perm = self.__check_permission(action, user,
145 perm = self.__check_permission(action, user,
146 repo_name)
146 repo_name)
147 if perm is not True:
147 if perm is not True:
148 return HTTPForbidden()(environ, start_response)
148 return HTTPForbidden()(environ, start_response)
149
149
150 extras = {'ip': ipaddr,
150 extras = {'ip': ipaddr,
151 'username': username,
151 'username': username,
152 'action': action,
152 'action': action,
153 'repository': repo_name}
153 'repository': repo_name}
154
154
155 #======================================================================
155 #======================================================================
156 # MERCURIAL REQUEST HANDLING
156 # MERCURIAL REQUEST HANDLING
157 #======================================================================
157 #======================================================================
158
158
159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
160 log.debug('Repository path is %s' % repo_path)
160 log.debug('Repository path is %s' % repo_path)
161
161
162 baseui = make_ui('db')
162 baseui = make_ui('db')
163 self.__inject_extras(repo_path, baseui, extras)
163 self.__inject_extras(repo_path, baseui, extras)
164
164
165
165
166 # quick check if that dir exists...
166 # quick check if that dir exists...
167 if is_valid_repo(repo_name, self.basepath) is False:
167 if is_valid_repo(repo_name, self.basepath) is False:
168 return HTTPNotFound()(environ, start_response)
168 return HTTPNotFound()(environ, start_response)
169
169
170 try:
170 try:
171 #invalidate cache on push
171 #invalidate cache on push
172 if action == 'push':
172 if action == 'push':
173 self.__invalidate_cache(repo_name)
173 self.__invalidate_cache(repo_name)
174
174
175 app = self.__make_app(repo_path, baseui, extras)
175 app = self.__make_app(repo_path, baseui, extras)
176 return app(environ, start_response)
176 return app(environ, start_response)
177 except RepoError, e:
177 except RepoError, e:
178 if str(e).find('not found') != -1:
178 if str(e).find('not found') != -1:
179 return HTTPNotFound()(environ, start_response)
179 return HTTPNotFound()(environ, start_response)
180 except Exception:
180 except Exception:
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 def __make_app(self, repo_name, baseui, extras):
184 def __make_app(self, repo_name, baseui, extras):
185 """
185 """
186 Make an wsgi application using hgweb, and inject generated baseui
186 Make an wsgi application using hgweb, and inject generated baseui
187 instance, additionally inject some extras into ui object
187 instance, additionally inject some extras into ui object
188 """
188 """
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
190
190
191
191
192 def __check_permission(self, action, user, repo_name):
192 def __check_permission(self, action, user, repo_name):
193 """
193 """
194 Checks permissions using action (push/pull) user and repository
194 Checks permissions using action (push/pull) user and repository
195 name
195 name
196
196
197 :param action: push or pull action
197 :param action: push or pull action
198 :param user: user instance
198 :param user: user instance
199 :param repo_name: repository name
199 :param repo_name: repository name
200 """
200 """
201 if action == 'push':
201 if action == 'push':
202 if not HasPermissionAnyMiddleware('repository.write',
202 if not HasPermissionAnyMiddleware('repository.write',
203 'repository.admin')(user,
203 'repository.admin')(user,
204 repo_name):
204 repo_name):
205 return False
205 return False
206
206
207 else:
207 else:
208 #any other action need at least read permission
208 #any other action need at least read permission
209 if not HasPermissionAnyMiddleware('repository.read',
209 if not HasPermissionAnyMiddleware('repository.read',
210 'repository.write',
210 'repository.write',
211 'repository.admin')(user,
211 'repository.admin')(user,
212 repo_name):
212 repo_name):
213 return False
213 return False
214
214
215 return True
215 return True
216
216
217 def __get_repository(self, environ):
217 def __get_repository(self, environ):
218 """
218 """
219 Get's repository name out of PATH_INFO header
219 Get's repository name out of PATH_INFO header
220
220
221 :param environ: environ where PATH_INFO is stored
221 :param environ: environ where PATH_INFO is stored
222 """
222 """
223 try:
223 try:
224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
225 if repo_name.endswith('/'):
225 if repo_name.endswith('/'):
226 repo_name = repo_name.rstrip('/')
226 repo_name = repo_name.rstrip('/')
227 except:
227 except:
228 log.error(traceback.format_exc())
228 log.error(traceback.format_exc())
229 raise
229 raise
230
230
231 return repo_name
231 return repo_name
232
232
233 def __get_user(self, username):
233 def __get_user(self, username):
234 return User.get_by_username(username)
234 return User.get_by_username(username)
235
235
236 def __get_action(self, environ):
236 def __get_action(self, environ):
237 """
237 """
238 Maps mercurial request commands into a clone,pull or push command.
238 Maps mercurial request commands into a clone,pull or push command.
239 This should always return a valid command string
239 This should always return a valid command string
240
240
241 :param environ:
241 :param environ:
242 """
242 """
243 mapping = {'changegroup': 'pull',
243 mapping = {'changegroup': 'pull',
244 'changegroupsubset': 'pull',
244 'changegroupsubset': 'pull',
245 'stream_out': 'pull',
245 'stream_out': 'pull',
246 'listkeys': 'pull',
246 'listkeys': 'pull',
247 'unbundle': 'push',
247 'unbundle': 'push',
248 'pushkey': 'push', }
248 'pushkey': 'push', }
249 for qry in environ['QUERY_STRING'].split('&'):
249 for qry in environ['QUERY_STRING'].split('&'):
250 if qry.startswith('cmd'):
250 if qry.startswith('cmd'):
251 cmd = qry.split('=')[-1]
251 cmd = qry.split('=')[-1]
252 if cmd in mapping:
252 if cmd in mapping:
253 return mapping[cmd]
253 return mapping[cmd]
254 else:
254 else:
255 return 'pull'
255 return 'pull'
256
256
257 def __invalidate_cache(self, repo_name):
257 def __invalidate_cache(self, repo_name):
258 """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
259 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
260 push requests"""
260 push requests"""
261 invalidate_cache('get_repo_cached_%s' % repo_name)
261 invalidate_cache('get_repo_cached_%s' % repo_name)
262
262
263 def __inject_extras(self, repo_path, baseui, extras={}):
263 def __inject_extras(self, repo_path, baseui, extras={}):
264 """
264 """
265 Injects some extra params into baseui instance
265 Injects some extra params into baseui instance
266
266
267 also overwrites global settings with those takes from local hgrc file
267 also overwrites global settings with those takes from local hgrc file
268
268
269 :param baseui: baseui instance
269 :param baseui: baseui instance
270 :param extras: dict with extra params to put into baseui
270 :param extras: dict with extra params to put into baseui
271 """
271 """
272
272
273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
274
274
275 # make our hgweb quiet so it doesn't print output
275 # make our hgweb quiet so it doesn't print output
276 baseui.setconfig('ui', 'quiet', 'true')
276 baseui.setconfig('ui', 'quiet', 'true')
277
277
278 #inject some additional parameters that will be available in ui
278 #inject some additional parameters that will be available in ui
279 #for hooks
279 #for hooks
280 for k, v in extras.items():
280 for k, v in extras.items():
281 baseui.setconfig('rhodecode_extras', k, v)
281 baseui.setconfig('rhodecode_extras', k, v)
282
282
283 repoui = make_ui('file', hgrc, False)
283 repoui = make_ui('file', hgrc, False)
284
284
285 if repoui:
285 if repoui:
286 #overwrite our ui instance with the section from hgrc file
286 #overwrite our ui instance with the section from hgrc file
287 for section in ui_sections:
287 for section in ui_sections:
288 for k, v in repoui.configitems(section):
288 for k, v in repoui.configitems(section):
289 baseui.setconfig(section, k, v)
289 baseui.setconfig(section, k, v)
290
290
General Comments 0
You need to be logged in to leave comments. Login now