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