##// END OF EJS Templates
i18n: use consistent way of setting user language.
marcink -
r1307:b929e98f default
parent child Browse files
Show More
@@ -1,604 +1,596 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import ipaddress
31 31 import pyramid.threadlocal
32 32
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36 from pylons import config, tmpl_context as c, request, session, url
37 37 from pylons.controllers import WSGIController
38 38 from pylons.controllers.util import redirect
39 39 from pylons.i18n import translation
40 40 # marcink: don't remove this import
41 41 from pylons.templating import render_mako as render # noqa
42 42 from pylons.i18n.translation import _
43 43 from webob.exc import HTTPFound
44 44
45 45
46 46 import rhodecode
47 47 from rhodecode.authentication.base import VCS_TYPE
48 48 from rhodecode.lib import auth, utils2
49 49 from rhodecode.lib import helpers as h
50 50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 51 from rhodecode.lib.exceptions import UserCreationError
52 52 from rhodecode.lib.utils import (
53 53 get_repo_slug, set_rhodecode_config, password_changed,
54 54 get_enabled_hook_classes)
55 55 from rhodecode.lib.utils2 import (
56 56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 58 from rhodecode.model import meta
59 59 from rhodecode.model.db import Repository, User
60 60 from rhodecode.model.notification import NotificationModel
61 61 from rhodecode.model.scm import ScmModel
62 62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63 63
64 64
65 65 log = logging.getLogger(__name__)
66 66
67 67
68 68 def _filter_proxy(ip):
69 69 """
70 70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 71 ips. Those comma separated IPs are passed from various proxies in the
72 72 chain of request processing. The left-most being the original client.
73 73 We only care about the first IP which came from the org. client.
74 74
75 75 :param ip: ip string from headers
76 76 """
77 77 if ',' in ip:
78 78 _ips = ip.split(',')
79 79 _first_ip = _ips[0].strip()
80 80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 81 return _first_ip
82 82 return ip
83 83
84 84
85 85 def _filter_port(ip):
86 86 """
87 87 Removes a port from ip, there are 4 main cases to handle here.
88 88 - ipv4 eg. 127.0.0.1
89 89 - ipv6 eg. ::1
90 90 - ipv4+port eg. 127.0.0.1:8080
91 91 - ipv6+port eg. [::1]:8080
92 92
93 93 :param ip:
94 94 """
95 95 def is_ipv6(ip_addr):
96 96 if hasattr(socket, 'inet_pton'):
97 97 try:
98 98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 99 except socket.error:
100 100 return False
101 101 else:
102 102 # fallback to ipaddress
103 103 try:
104 104 ipaddress.IPv6Address(ip_addr)
105 105 except Exception:
106 106 return False
107 107 return True
108 108
109 109 if ':' not in ip: # must be ipv4 pure ip
110 110 return ip
111 111
112 112 if '[' in ip and ']' in ip: # ipv6 with port
113 113 return ip.split(']')[0][1:].lower()
114 114
115 115 # must be ipv6 or ipv4 with port
116 116 if is_ipv6(ip):
117 117 return ip
118 118 else:
119 119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 120 return ip
121 121
122 122
123 123 def get_ip_addr(environ):
124 124 proxy_key = 'HTTP_X_REAL_IP'
125 125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 126 def_key = 'REMOTE_ADDR'
127 127 _filters = lambda x: _filter_port(_filter_proxy(x))
128 128
129 129 ip = environ.get(proxy_key)
130 130 if ip:
131 131 return _filters(ip)
132 132
133 133 ip = environ.get(proxy_key2)
134 134 if ip:
135 135 return _filters(ip)
136 136
137 137 ip = environ.get(def_key, '0.0.0.0')
138 138 return _filters(ip)
139 139
140 140
141 141 def get_server_ip_addr(environ, log_errors=True):
142 142 hostname = environ.get('SERVER_NAME')
143 143 try:
144 144 return socket.gethostbyname(hostname)
145 145 except Exception as e:
146 146 if log_errors:
147 147 # in some cases this lookup is not possible, and we don't want to
148 148 # make it an exception in logs
149 149 log.exception('Could not retrieve server ip address: %s', e)
150 150 return hostname
151 151
152 152
153 153 def get_server_port(environ):
154 154 return environ.get('SERVER_PORT')
155 155
156 156
157 157 def get_access_path(environ):
158 158 path = environ.get('PATH_INFO')
159 159 org_req = environ.get('pylons.original_request')
160 160 if org_req:
161 161 path = org_req.environ.get('PATH_INFO')
162 162 return path
163 163
164 164
165 165 def vcs_operation_context(
166 166 environ, repo_name, username, action, scm, check_locking=True,
167 167 is_shadow_repo=False):
168 168 """
169 169 Generate the context for a vcs operation, e.g. push or pull.
170 170
171 171 This context is passed over the layers so that hooks triggered by the
172 172 vcs operation know details like the user, the user's IP address etc.
173 173
174 174 :param check_locking: Allows to switch of the computation of the locking
175 175 data. This serves mainly the need of the simplevcs middleware to be
176 176 able to disable this for certain operations.
177 177
178 178 """
179 179 # Tri-state value: False: unlock, None: nothing, True: lock
180 180 make_lock = None
181 181 locked_by = [None, None, None]
182 182 is_anonymous = username == User.DEFAULT_USER
183 183 if not is_anonymous and check_locking:
184 184 log.debug('Checking locking on repository "%s"', repo_name)
185 185 user = User.get_by_username(username)
186 186 repo = Repository.get_by_repo_name(repo_name)
187 187 make_lock, __, locked_by = repo.get_locking_state(
188 188 action, user.user_id)
189 189
190 190 settings_model = VcsSettingsModel(repo=repo_name)
191 191 ui_settings = settings_model.get_ui_settings()
192 192
193 193 extras = {
194 194 'ip': get_ip_addr(environ),
195 195 'username': username,
196 196 'action': action,
197 197 'repository': repo_name,
198 198 'scm': scm,
199 199 'config': rhodecode.CONFIG['__file__'],
200 200 'make_lock': make_lock,
201 201 'locked_by': locked_by,
202 202 'server_url': utils2.get_server_url(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 }
206 206 return extras
207 207
208 208
209 209 class BasicAuth(AuthBasicAuthenticator):
210 210
211 211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 212 initial_call_detection=False):
213 213 self.realm = realm
214 214 self.initial_call = initial_call_detection
215 215 self.authfunc = authfunc
216 216 self.registry = registry
217 217 self._rc_auth_http_code = auth_http_code
218 218
219 219 def _get_response_from_code(self, http_code):
220 220 try:
221 221 return get_exception(safe_int(http_code))
222 222 except Exception:
223 223 log.exception('Failed to fetch response for code %s' % http_code)
224 224 return HTTPForbidden
225 225
226 226 def build_authentication(self):
227 227 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
228 228 if self._rc_auth_http_code and not self.initial_call:
229 229 # return alternative HTTP code if alternative http return code
230 230 # is specified in RhodeCode config, but ONLY if it's not the
231 231 # FIRST call
232 232 custom_response_klass = self._get_response_from_code(
233 233 self._rc_auth_http_code)
234 234 return custom_response_klass(headers=head)
235 235 return HTTPUnauthorized(headers=head)
236 236
237 237 def authenticate(self, environ):
238 238 authorization = AUTHORIZATION(environ)
239 239 if not authorization:
240 240 return self.build_authentication()
241 241 (authmeth, auth) = authorization.split(' ', 1)
242 242 if 'basic' != authmeth.lower():
243 243 return self.build_authentication()
244 244 auth = auth.strip().decode('base64')
245 245 _parts = auth.split(':', 1)
246 246 if len(_parts) == 2:
247 247 username, password = _parts
248 248 if self.authfunc(
249 249 username, password, environ, VCS_TYPE,
250 250 registry=self.registry):
251 251 return username
252 252 if username and password:
253 253 # we mark that we actually executed authentication once, at
254 254 # that point we can use the alternative auth code
255 255 self.initial_call = False
256 256
257 257 return self.build_authentication()
258 258
259 259 __call__ = authenticate
260 260
261 261
262 262 def attach_context_attributes(context, request):
263 263 """
264 264 Attach variables into template context called `c`, please note that
265 265 request could be pylons or pyramid request in here.
266 266 """
267 267 rc_config = SettingsModel().get_all_settings(cache=True)
268 268
269 269 context.rhodecode_version = rhodecode.__version__
270 270 context.rhodecode_edition = config.get('rhodecode.edition')
271 271 # unique secret + version does not leak the version but keep consistency
272 272 context.rhodecode_version_hash = md5(
273 273 config.get('beaker.session.secret', '') +
274 274 rhodecode.__version__)[:8]
275 275
276 276 # Default language set for the incoming request
277 277 context.language = translation.get_lang()[0]
278 278
279 279 # Visual options
280 280 context.visual = AttributeDict({})
281 281
282 282 # DB stored Visual Items
283 283 context.visual.show_public_icon = str2bool(
284 284 rc_config.get('rhodecode_show_public_icon'))
285 285 context.visual.show_private_icon = str2bool(
286 286 rc_config.get('rhodecode_show_private_icon'))
287 287 context.visual.stylify_metatags = str2bool(
288 288 rc_config.get('rhodecode_stylify_metatags'))
289 289 context.visual.dashboard_items = safe_int(
290 290 rc_config.get('rhodecode_dashboard_items', 100))
291 291 context.visual.admin_grid_items = safe_int(
292 292 rc_config.get('rhodecode_admin_grid_items', 100))
293 293 context.visual.repository_fields = str2bool(
294 294 rc_config.get('rhodecode_repository_fields'))
295 295 context.visual.show_version = str2bool(
296 296 rc_config.get('rhodecode_show_version'))
297 297 context.visual.use_gravatar = str2bool(
298 298 rc_config.get('rhodecode_use_gravatar'))
299 299 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
300 300 context.visual.default_renderer = rc_config.get(
301 301 'rhodecode_markup_renderer', 'rst')
302 302 context.visual.rhodecode_support_url = \
303 303 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
304 304
305 305 context.pre_code = rc_config.get('rhodecode_pre_code')
306 306 context.post_code = rc_config.get('rhodecode_post_code')
307 307 context.rhodecode_name = rc_config.get('rhodecode_title')
308 308 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
309 309 # if we have specified default_encoding in the request, it has more
310 310 # priority
311 311 if request.GET.get('default_encoding'):
312 312 context.default_encodings.insert(0, request.GET.get('default_encoding'))
313 313 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
314 314
315 315 # INI stored
316 316 context.labs_active = str2bool(
317 317 config.get('labs_settings_active', 'false'))
318 318 context.visual.allow_repo_location_change = str2bool(
319 319 config.get('allow_repo_location_change', True))
320 320 context.visual.allow_custom_hooks_settings = str2bool(
321 321 config.get('allow_custom_hooks_settings', True))
322 322 context.debug_style = str2bool(config.get('debug_style', False))
323 323
324 324 context.rhodecode_instanceid = config.get('instance_id')
325 325
326 326 # AppEnlight
327 327 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
328 328 context.appenlight_api_public_key = config.get(
329 329 'appenlight.api_public_key', '')
330 330 context.appenlight_server_url = config.get('appenlight.server_url', '')
331 331
332 332 # JS template context
333 333 context.template_context = {
334 334 'repo_name': None,
335 335 'repo_type': None,
336 336 'repo_landing_commit': None,
337 337 'rhodecode_user': {
338 338 'username': None,
339 339 'email': None,
340 340 'notification_status': False
341 341 },
342 342 'visual': {
343 343 'default_renderer': None
344 344 },
345 345 'commit_data': {
346 346 'commit_id': None
347 347 },
348 348 'pull_request_data': {'pull_request_id': None},
349 349 'timeago': {
350 350 'refresh_time': 120 * 1000,
351 351 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
352 352 },
353 353 'pylons_dispatch': {
354 354 # 'controller': request.environ['pylons.routes_dict']['controller'],
355 355 # 'action': request.environ['pylons.routes_dict']['action'],
356 356 },
357 357 'pyramid_dispatch': {
358 358
359 359 },
360 360 'extra': {'plugins': {}}
361 361 }
362 362 # END CONFIG VARS
363 363
364 364 # TODO: This dosn't work when called from pylons compatibility tween.
365 365 # Fix this and remove it from base controller.
366 366 # context.repo_name = get_repo_slug(request) # can be empty
367 367
368 368 diffmode = 'sideside'
369 369 if request.GET.get('diffmode'):
370 370 if request.GET['diffmode'] == 'unified':
371 371 diffmode = 'unified'
372 372 elif request.session.get('diffmode'):
373 373 diffmode = request.session['diffmode']
374 374
375 375 context.diffmode = diffmode
376 376
377 377 if request.session.get('diffmode') != diffmode:
378 378 request.session['diffmode'] = diffmode
379 379
380 380 context.csrf_token = auth.get_csrf_token()
381 381 context.backends = rhodecode.BACKENDS.keys()
382 382 context.backends.sort()
383 383 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
384 384 context.rhodecode_user.user_id)
385 385
386 386 context.pyramid_request = pyramid.threadlocal.get_current_request()
387 387
388 388
389 389 def get_auth_user(environ):
390 390 ip_addr = get_ip_addr(environ)
391 391 # make sure that we update permissions each time we call controller
392 392 _auth_token = (request.GET.get('auth_token', '') or
393 393 request.GET.get('api_key', ''))
394 394
395 395 if _auth_token:
396 396 # when using API_KEY we assume user exists, and
397 397 # doesn't need auth based on cookies.
398 398 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
399 399 authenticated = False
400 400 else:
401 401 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
402 402 try:
403 403 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
404 404 ip_addr=ip_addr)
405 405 except UserCreationError as e:
406 406 h.flash(e, 'error')
407 407 # container auth or other auth functions that create users
408 408 # on the fly can throw this exception signaling that there's
409 409 # issue with user creation, explanation should be provided
410 410 # in Exception itself. We then create a simple blank
411 411 # AuthUser
412 412 auth_user = AuthUser(ip_addr=ip_addr)
413 413
414 414 if password_changed(auth_user, session):
415 415 session.invalidate()
416 416 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
417 417 auth_user = AuthUser(ip_addr=ip_addr)
418 418
419 419 authenticated = cookie_store.get('is_authenticated')
420 420
421 421 if not auth_user.is_authenticated and auth_user.is_user_object:
422 422 # user is not authenticated and not empty
423 423 auth_user.set_authenticated(authenticated)
424 424
425 425 return auth_user
426 426
427 427
428 428 class BaseController(WSGIController):
429 429
430 430 def __before__(self):
431 431 """
432 432 __before__ is called before controller methods and after __call__
433 433 """
434 434 # on each call propagate settings calls into global settings.
435 435 set_rhodecode_config(config)
436 436 attach_context_attributes(c, request)
437 437
438 438 # TODO: Remove this when fixed in attach_context_attributes()
439 439 c.repo_name = get_repo_slug(request) # can be empty
440 440
441 441 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
442 442 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
443 443 self.sa = meta.Session
444 444 self.scm_model = ScmModel(self.sa)
445 445
446 default_lang = c.language
447 user_lang = c.language
448 try:
449 user_obj = self._rhodecode_user.get_instance()
450 if user_obj:
451 user_lang = user_obj.user_data.get('language')
452 except Exception:
453 log.exception('Failed to fetch user language for user %s',
454 self._rhodecode_user)
455
456 if user_lang and user_lang != default_lang:
457 log.debug('set language to %s for user %s', user_lang,
458 self._rhodecode_user)
446 # set user language
447 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
448 if user_lang:
459 449 translation.set_lang(user_lang)
450 log.debug('set language to %s for user %s',
451 user_lang, self._rhodecode_user)
460 452
461 453 def _dispatch_redirect(self, with_url, environ, start_response):
462 454 resp = HTTPFound(with_url)
463 455 environ['SCRIPT_NAME'] = '' # handle prefix middleware
464 456 environ['PATH_INFO'] = with_url
465 457 return resp(environ, start_response)
466 458
467 459 def __call__(self, environ, start_response):
468 460 """Invoke the Controller"""
469 461 # WSGIController.__call__ dispatches to the Controller method
470 462 # the request is routed to. This routing information is
471 463 # available in environ['pylons.routes_dict']
472 464 from rhodecode.lib import helpers as h
473 465
474 466 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
475 467 if environ.get('debugtoolbar.wants_pylons_context', False):
476 468 environ['debugtoolbar.pylons_context'] = c._current_obj()
477 469
478 470 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
479 471 environ['pylons.routes_dict']['action']])
480 472
481 473 self.rc_config = SettingsModel().get_all_settings(cache=True)
482 474 self.ip_addr = get_ip_addr(environ)
483 475
484 476 # The rhodecode auth user is looked up and passed through the
485 477 # environ by the pylons compatibility tween in pyramid.
486 478 # So we can just grab it from there.
487 479 auth_user = environ['rc_auth_user']
488 480
489 481 # set globals for auth user
490 482 request.user = auth_user
491 483 c.rhodecode_user = self._rhodecode_user = auth_user
492 484
493 485 log.info('IP: %s User: %s accessed %s [%s]' % (
494 486 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
495 487 _route_name)
496 488 )
497 489
498 490 # TODO: Maybe this should be move to pyramid to cover all views.
499 491 # check user attributes for password change flag
500 492 user_obj = auth_user.get_instance()
501 493 if user_obj and user_obj.user_data.get('force_password_change'):
502 494 h.flash('You are required to change your password', 'warning',
503 495 ignore_duplicate=True)
504 496
505 497 skip_user_check_urls = [
506 498 'error.document', 'login.logout', 'login.index',
507 499 'admin/my_account.my_account_password',
508 500 'admin/my_account.my_account_password_update'
509 501 ]
510 502 if _route_name not in skip_user_check_urls:
511 503 return self._dispatch_redirect(
512 504 url('my_account_password'), environ, start_response)
513 505
514 506 return WSGIController.__call__(self, environ, start_response)
515 507
516 508
517 509 class BaseRepoController(BaseController):
518 510 """
519 511 Base class for controllers responsible for loading all needed data for
520 512 repository loaded items are
521 513
522 514 c.rhodecode_repo: instance of scm repository
523 515 c.rhodecode_db_repo: instance of db
524 516 c.repository_requirements_missing: shows that repository specific data
525 517 could not be displayed due to the missing requirements
526 518 c.repository_pull_requests: show number of open pull requests
527 519 """
528 520
529 521 def __before__(self):
530 522 super(BaseRepoController, self).__before__()
531 523 if c.repo_name: # extracted from routes
532 524 db_repo = Repository.get_by_repo_name(c.repo_name)
533 525 if not db_repo:
534 526 return
535 527
536 528 log.debug(
537 529 'Found repository in database %s with state `%s`',
538 530 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
539 531 route = getattr(request.environ.get('routes.route'), 'name', '')
540 532
541 533 # allow to delete repos that are somehow damages in filesystem
542 534 if route in ['delete_repo']:
543 535 return
544 536
545 537 if db_repo.repo_state in [Repository.STATE_PENDING]:
546 538 if route in ['repo_creating_home']:
547 539 return
548 540 check_url = url('repo_creating_home', repo_name=c.repo_name)
549 541 return redirect(check_url)
550 542
551 543 self.rhodecode_db_repo = db_repo
552 544
553 545 missing_requirements = False
554 546 try:
555 547 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
556 548 except RepositoryRequirementError as e:
557 549 missing_requirements = True
558 550 self._handle_missing_requirements(e)
559 551
560 552 if self.rhodecode_repo is None and not missing_requirements:
561 553 log.error('%s this repository is present in database but it '
562 554 'cannot be created as an scm instance', c.repo_name)
563 555
564 556 h.flash(_(
565 557 "The repository at %(repo_name)s cannot be located.") %
566 558 {'repo_name': c.repo_name},
567 559 category='error', ignore_duplicate=True)
568 560 redirect(url('home'))
569 561
570 562 # update last change according to VCS data
571 563 if not missing_requirements:
572 564 commit = db_repo.get_commit(
573 565 pre_load=["author", "date", "message", "parents"])
574 566 db_repo.update_commit_cache(commit)
575 567
576 568 # Prepare context
577 569 c.rhodecode_db_repo = db_repo
578 570 c.rhodecode_repo = self.rhodecode_repo
579 571 c.repository_requirements_missing = missing_requirements
580 572
581 573 self._update_global_counters(self.scm_model, db_repo)
582 574
583 575 def _update_global_counters(self, scm_model, db_repo):
584 576 """
585 577 Base variables that are exposed to every page of repository
586 578 """
587 579 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
588 580
589 581 def _handle_missing_requirements(self, error):
590 582 self.rhodecode_repo = None
591 583 log.error(
592 584 'Requirements are missing for repository %s: %s',
593 585 c.repo_name, error.message)
594 586
595 587 summary_url = url('summary_home', repo_name=c.repo_name)
596 588 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
597 589 settings_update_url = url('repo', repo_name=c.repo_name)
598 590 path = request.path
599 591 should_redirect = (
600 592 path not in (summary_url, settings_update_url)
601 593 and '/settings' not in path or path == statistics_url
602 594 )
603 595 if should_redirect:
604 596 redirect(summary_url)
@@ -1,157 +1,166 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import pylons
24 24 import Queue
25 25 import subprocess32
26 26
27 27 from pyramid.i18n import get_localizer
28 28 from pyramid.threadlocal import get_current_request
29 29 from threading import Thread
30 30
31 31 from rhodecode.translation import _ as tsf
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def add_renderer_globals(event):
38 38 # Put pylons stuff into the context. This will be removed as soon as
39 39 # migration to pyramid is finished.
40 40 conf = pylons.config._current_obj()
41 41 event['h'] = conf.get('pylons.h')
42 42 event['c'] = pylons.tmpl_context
43 43 event['url'] = pylons.url
44 44
45 45 # TODO: When executed in pyramid view context the request is not available
46 46 # in the event. Find a better solution to get the request.
47 47 request = event['request'] or get_current_request()
48 48
49 49 # Add Pyramid translation as '_' to context
50 50 event['_'] = request.translate
51 51 event['_ungettext'] = request.plularize
52 52
53 53
54 54 def add_localizer(event):
55 55 request = event.request
56 56 localizer = get_localizer(request)
57 57
58 58 def auto_translate(*args, **kwargs):
59 59 return localizer.translate(tsf(*args, **kwargs))
60 60
61 61 request.localizer = localizer
62 62 request.translate = auto_translate
63 63 request.plularize = localizer.pluralize
64 64
65 65
66 def set_user_lang(event):
67 cur_user = getattr(event.request, 'user', None)
68
69 if cur_user:
70 user_lang = cur_user.get_instance().user_data.get('language')
71 if user_lang:
72 event.request._LOCALE_ = user_lang
73
74
66 75 def scan_repositories_if_enabled(event):
67 76 """
68 77 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
69 78 does a repository scan if enabled in the settings.
70 79 """
71 80 from rhodecode.model.scm import ScmModel
72 81 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
73 82 settings = event.app.registry.settings
74 83 vcs_server_enabled = settings['vcs.server.enable']
75 84 import_on_startup = settings['startup.import_repos']
76 85 if vcs_server_enabled and import_on_startup:
77 86 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
78 87 repo2db_mapper(repositories, remove_obsolete=False)
79 88
80 89
81 90 class Subscriber(object):
82 91 """
83 92 Base class for subscribers to the pyramid event system.
84 93 """
85 94 def __call__(self, event):
86 95 self.run(event)
87 96
88 97 def run(self, event):
89 98 raise NotImplementedError('Subclass has to implement this.')
90 99
91 100
92 101 class AsyncSubscriber(Subscriber):
93 102 """
94 103 Subscriber that handles the execution of events in a separate task to not
95 104 block the execution of the code which triggers the event. It puts the
96 105 received events into a queue from which the worker process takes them in
97 106 order.
98 107 """
99 108 def __init__(self):
100 109 self._stop = False
101 110 self._eventq = Queue.Queue()
102 111 self._worker = self.create_worker()
103 112 self._worker.start()
104 113
105 114 def __call__(self, event):
106 115 self._eventq.put(event)
107 116
108 117 def create_worker(self):
109 118 worker = Thread(target=self.do_work)
110 119 worker.daemon = True
111 120 return worker
112 121
113 122 def stop_worker(self):
114 123 self._stop = False
115 124 self._eventq.put(None)
116 125 self._worker.join()
117 126
118 127 def do_work(self):
119 128 while not self._stop:
120 129 event = self._eventq.get()
121 130 if event is not None:
122 131 self.run(event)
123 132
124 133
125 134 class AsyncSubprocessSubscriber(AsyncSubscriber):
126 135 """
127 136 Subscriber that uses the subprocess32 module to execute a command if an
128 137 event is received. Events are handled asynchronously.
129 138 """
130 139
131 140 def __init__(self, cmd, timeout=None):
132 141 super(AsyncSubprocessSubscriber, self).__init__()
133 142 self._cmd = cmd
134 143 self._timeout = timeout
135 144
136 145 def run(self, event):
137 146 cmd = self._cmd
138 147 timeout = self._timeout
139 148 log.debug('Executing command %s.', cmd)
140 149
141 150 try:
142 151 output = subprocess32.check_output(
143 152 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
144 153 log.debug('Command finished %s', cmd)
145 154 if output:
146 155 log.debug('Command output: %s', output)
147 156 except subprocess32.TimeoutExpired as e:
148 157 log.exception('Timeout while executing command.')
149 158 if e.output:
150 159 log.error('Command output: %s', e.output)
151 160 except subprocess32.CalledProcessError as e:
152 161 log.exception('Error while executing command.')
153 162 if e.output:
154 163 log.error('Command output: %s', e.output)
155 164 except:
156 165 log.exception(
157 166 'Exception while executing command %s.', cmd)
@@ -1,97 +1,98 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import pylons
24 24 import rhodecode
25 25
26 26 from pylons.i18n.translation import _get_translator
27 27 from pylons.util import ContextObj
28 28 from routes.util import URLGenerator
29 29
30 30 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 31 from rhodecode.lib.middleware.vcs import (
32 32 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
33 33 from rhodecode.model import meta
34 34
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 def pylons_compatibility_tween_factory(handler, registry):
40 40
41 41 def pylons_compatibility_tween(request):
42 42 """
43 43 While migrating from pylons to pyramid we need to call some pylons code
44 44 from pyramid. For example while rendering an old template that uses the
45 45 'c' or 'h' objects. This tween sets up the needed pylons globals.
46 46 """
47 47 config = rhodecode.CONFIG
48 48 environ = request.environ
49 49 session = request.session
50 50
51 51 vcs_handler = detect_vcs_request(
52 52 request.environ, request.registry.settings.get('vcs.backends'))
53 53
54 54 if vcs_handler:
55 55 # save detected VCS type for later re-use
56 56 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
57 57 return handler(request)
58 58
59 59 # mark that we didn't detect an VCS, and we can skip detection later on
60 60 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
61 61
62 62 # Setup pylons globals.
63 63 pylons.config._push_object(config)
64 64 pylons.request._push_object(request)
65 65 pylons.session._push_object(session)
66 66
67 67 session_key = (
68 68 config['pylons.environ_config'].get('session', 'beaker.session'))
69 69 environ[session_key] = session
70 pylons.url._push_object(URLGenerator(config['routes.map'],
71 environ))
70 pylons.url._push_object(URLGenerator(config['routes.map'], environ))
72 71
73 72 # TODO: Maybe we should use the language from pyramid.
74 73 translator = _get_translator(config.get('lang'))
75 74 pylons.translator._push_object(translator)
76 75
77 76 # Get the rhodecode auth user object and make it available.
78 77 auth_user = get_auth_user(environ)
79 78 request.user = auth_user
80 79 environ['rc_auth_user'] = auth_user
81 80
82 81 # Setup the pylons context object ('c')
83 82 context = ContextObj()
84 83 context.rhodecode_user = auth_user
85 84 attach_context_attributes(context, request)
86 85 pylons.tmpl_context._push_object(context)
87 86 return handler(request)
88 87
89 88 return pylons_compatibility_tween
90 89
91 90
92 91 def includeme(config):
93 92 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
94 93 'pyramid.events.BeforeRender')
94 config.add_subscriber('rhodecode.subscribers.set_user_lang',
95 'pyramid.events.NewRequest')
95 96 config.add_subscriber('rhodecode.subscribers.add_localizer',
96 97 'pyramid.events.NewRequest')
97 98 config.add_tween('rhodecode.tweens.pylons_compatibility_tween_factory')
General Comments 0
You need to be logged in to leave comments. Login now