##// END OF EJS Templates
small rewrite for injecting rhodecode_extras into ui. instead of injecting to hgweb instance, inject to ui and pass to hgweb. It's simpler and more readable.
marcink -
r1276:88e75052 beta
parent child Browse files
Show More
@@ -1,271 +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
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 #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,
98 anonymous_perm = self.__check_permission(self.action,
99 anonymous_user,
99 anonymous_user,
100 self.repo_name)
100 self.repo_name)
101
101
102 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:
103 if anonymous_perm is not True:
103 if anonymous_perm is not True:
104 log.debug('Not enough credentials to access this '
104 log.debug('Not enough credentials to access this '
105 'repository as anonymous user')
105 'repository as anonymous user')
106 if anonymous_user.active is False:
106 if anonymous_user.active is False:
107 log.debug('Anonymous access is disabled, running '
107 log.debug('Anonymous access is disabled, running '
108 'authentication')
108 'authentication')
109 #==============================================================
109 #==============================================================
110 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
110 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
111 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
111 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
112 #==============================================================
112 #==============================================================
113
113
114 if not REMOTE_USER(environ):
114 if not REMOTE_USER(environ):
115 self.authenticate.realm = str(
115 self.authenticate.realm = str(
116 self.config['rhodecode_realm'])
116 self.config['rhodecode_realm'])
117 result = self.authenticate(environ)
117 result = self.authenticate(environ)
118 if isinstance(result, str):
118 if isinstance(result, str):
119 AUTH_TYPE.update(environ, 'basic')
119 AUTH_TYPE.update(environ, 'basic')
120 REMOTE_USER.update(environ, result)
120 REMOTE_USER.update(environ, result)
121 else:
121 else:
122 return result.wsgi_application(environ, start_response)
122 return result.wsgi_application(environ, start_response)
123
123
124 #==============================================================
124 #==============================================================
125 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
125 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
126 # BASIC AUTH
126 # BASIC AUTH
127 #==============================================================
127 #==============================================================
128
128
129 if self.action in ['pull', 'push']:
129 if self.action in ['pull', 'push']:
130 username = REMOTE_USER(environ)
130 username = REMOTE_USER(environ)
131 try:
131 try:
132 user = self.__get_user(username)
132 user = self.__get_user(username)
133 self.username = user.username
133 self.username = user.username
134 except:
134 except:
135 log.error(traceback.format_exc())
135 log.error(traceback.format_exc())
136 return HTTPInternalServerError()(environ,
136 return HTTPInternalServerError()(environ,
137 start_response)
137 start_response)
138
138
139 #check permissions for this repository
139 #check permissions for this repository
140 perm = self.__check_permission(self.action, user,
140 perm = self.__check_permission(self.action, user,
141 self.repo_name)
141 self.repo_name)
142 if perm is not True:
142 if perm is not True:
143 return HTTPForbidden()(environ, start_response)
143 return HTTPForbidden()(environ, start_response)
144
144
145 self.extras = {'ip': self.ipaddr,
145 self.extras = {'ip': self.ipaddr,
146 'username': self.username,
146 'username': self.username,
147 'action': self.action,
147 'action': self.action,
148 'repository': self.repo_name}
148 'repository': self.repo_name}
149
149
150 #======================================================================
150 #======================================================================
151 # MERCURIAL REQUEST HANDLING
151 # MERCURIAL REQUEST HANDLING
152 #======================================================================
152 #======================================================================
153 environ['PATH_INFO'] = '/' # since we wrap into hgweb, reset the path
153 environ['PATH_INFO'] = '/' # since we wrap into hgweb, reset the path
154 self.baseui = make_ui('db')
154 self.baseui = make_ui('db')
155 self.basepath = self.config['base_path']
155 self.basepath = self.config['base_path']
156 self.repo_path = os.path.join(self.basepath, self.repo_name)
156 self.repo_path = os.path.join(self.basepath, self.repo_name)
157
157
158 #quick check if that dir exists...
158 #quick check if that dir exists...
159 if check_repo_fast(self.repo_name, self.basepath):
159 if check_repo_fast(self.repo_name, self.basepath):
160 return HTTPNotFound()(environ, start_response)
160 return HTTPNotFound()(environ, start_response)
161 try:
161 try:
162 app = wsgiapplication(self.__make_app)
162 app = wsgiapplication(self.__make_app)
163 except RepoError, e:
163 except RepoError, e:
164 if str(e).find('not found') != -1:
164 if str(e).find('not found') != -1:
165 return HTTPNotFound()(environ, start_response)
165 return HTTPNotFound()(environ, start_response)
166 except Exception:
166 except Exception:
167 log.error(traceback.format_exc())
167 log.error(traceback.format_exc())
168 return HTTPInternalServerError()(environ, start_response)
168 return HTTPInternalServerError()(environ, start_response)
169
169
170 #invalidate cache on push
170 #invalidate cache on push
171 if self.action == 'push':
171 if self.action == 'push':
172 self.__invalidate_cache(self.repo_name)
172 self.__invalidate_cache(self.repo_name)
173
173
174 return app(environ, start_response)
174 return app(environ, start_response)
175
175
176 def __make_app(self):
176 def __make_app(self):
177 """Make an wsgi application using hgweb, and my generated baseui
177 """
178 instance
178 Make an wsgi application using hgweb, and inject generated baseui
179 instance, additionally inject some extras into ui object
179 """
180 """
181 self.__inject_extras(self.baseui, self.extras)
182 return hgweb(str(self.repo_path), baseui=self.baseui)
180
183
181 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
182 return self.__load_web_settings(hgserve, self.extras)
183
184
184 def __check_permission(self, action, user, repo_name):
185 def __check_permission(self, action, user, repo_name):
185 """Checks permissions using action (push/pull) user and repository
186 """
187 Checks permissions using action (push/pull) user and repository
186 name
188 name
187
189
188 :param action: push or pull action
190 :param action: push or pull action
189 :param user: user instance
191 :param user: user instance
190 :param repo_name: repository name
192 :param repo_name: repository name
191 """
193 """
192 if action == 'push':
194 if action == 'push':
193 if not HasPermissionAnyMiddleware('repository.write',
195 if not HasPermissionAnyMiddleware('repository.write',
194 'repository.admin')(user,
196 'repository.admin')(user,
195 repo_name):
197 repo_name):
196 return False
198 return False
197
199
198 else:
200 else:
199 #any other action need at least read permission
201 #any other action need at least read permission
200 if not HasPermissionAnyMiddleware('repository.read',
202 if not HasPermissionAnyMiddleware('repository.read',
201 'repository.write',
203 'repository.write',
202 'repository.admin')(user,
204 'repository.admin')(user,
203 repo_name):
205 repo_name):
204 return False
206 return False
205
207
206 return True
208 return True
207
209
208 def __get_repository(self, environ):
210 def __get_repository(self, environ):
209 """Get's repository name out of PATH_INFO header
211 """
212 Get's repository name out of PATH_INFO header
210
213
211 :param environ: environ where PATH_INFO is stored
214 :param environ: environ where PATH_INFO is stored
212 """
215 """
213 try:
216 try:
214 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
217 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
215 if repo_name.endswith('/'):
218 if repo_name.endswith('/'):
216 repo_name = repo_name.rstrip('/')
219 repo_name = repo_name.rstrip('/')
217 except:
220 except:
218 log.error(traceback.format_exc())
221 log.error(traceback.format_exc())
219 raise
222 raise
220
223
221 return repo_name
224 return repo_name
222
225
223 def __get_user(self, username):
226 def __get_user(self, username):
224 return UserModel().get_by_username(username, cache=True)
227 return UserModel().get_by_username(username, cache=True)
225
228
226 def __get_action(self, environ):
229 def __get_action(self, environ):
227 """Maps mercurial request commands into a clone,pull or push command.
230 """
231 Maps mercurial request commands into a clone,pull or push command.
228 This should always return a valid command string
232 This should always return a valid command string
229
233
230 :param environ:
234 :param environ:
231 """
235 """
232 mapping = {'changegroup': 'pull',
236 mapping = {'changegroup': 'pull',
233 'changegroupsubset': 'pull',
237 'changegroupsubset': 'pull',
234 'stream_out': 'pull',
238 'stream_out': 'pull',
235 'listkeys': 'pull',
239 'listkeys': 'pull',
236 'unbundle': 'push',
240 'unbundle': 'push',
237 'pushkey': 'push', }
241 'pushkey': 'push', }
238 for qry in environ['QUERY_STRING'].split('&'):
242 for qry in environ['QUERY_STRING'].split('&'):
239 if qry.startswith('cmd'):
243 if qry.startswith('cmd'):
240 cmd = qry.split('=')[-1]
244 cmd = qry.split('=')[-1]
241 if cmd in mapping:
245 if cmd in mapping:
242 return mapping[cmd]
246 return mapping[cmd]
243 else:
247 else:
244 return 'pull'
248 return 'pull'
245
249
246 def __invalidate_cache(self, repo_name):
250 def __invalidate_cache(self, repo_name):
247 """we know that some change was made to repositories and we should
251 """we know that some change was made to repositories and we should
248 invalidate the cache to see the changes right away but only for
252 invalidate the cache to see the changes right away but only for
249 push requests"""
253 push requests"""
250 invalidate_cache('get_repo_cached_%s' % repo_name)
254 invalidate_cache('get_repo_cached_%s' % repo_name)
251
255
252 def __load_web_settings(self, hgserve, extras={}):
256 def __inject_extras(self, baseui, extras={}):
253 #set the global ui for hgserve instance passed
254 hgserve.repo.ui = self.baseui
255
257
256 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
258 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
257
259
258 #inject some additional parameters that will be available in ui
260 #inject some additional parameters that will be available in ui
259 #for hooks
261 #for hooks
260 for k, v in extras.items():
262 for k, v in extras.items():
261 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
263 baseui.setconfig('rhodecode_extras', k, v)
262
264
263 repoui = make_ui('file', hgrc, False)
265 repoui = make_ui('file', hgrc, False)
264
266
265 if repoui:
267 if repoui:
266 #overwrite our ui instance with the section from hgrc file
268 #overwrite our ui instance with the section from hgrc file
267 for section in ui_sections:
269 for section in ui_sections:
268 for k, v in repoui.configitems(section):
270 for k, v in repoui.configitems(section):
269 hgserve.repo.ui.setconfig(section, k, v)
271 baseui.repo.ui.setconfig(section, k, v)
270
271 return hgserve
General Comments 0
You need to be logged in to leave comments. Login now