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