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