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