##// END OF EJS Templates
core: handle bogus GET params so we don't crash on translate subscriber.
marcink -
r3132:fb303e8b default
parent child Browse files
Show More
@@ -1,329 +1,341 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 import io
20 import io
21 import re
21 import re
22 import os
22 import datetime
23 import datetime
23 import logging
24 import logging
24 import Queue
25 import Queue
25 import subprocess32
26 import subprocess32
26 import os
27
27
28
28
29 from dateutil.parser import parse
29 from dateutil.parser import parse
30 from pyramid.i18n import get_localizer
30 from pyramid.httpexceptions import HTTPBadRequest
31 from pyramid.threadlocal import get_current_request
31 from pyramid.threadlocal import get_current_request
32 from pyramid.interfaces import IRoutesMapper
32 from pyramid.interfaces import IRoutesMapper
33 from pyramid.settings import asbool
33 from pyramid.settings import asbool
34 from pyramid.path import AssetResolver
34 from pyramid.path import AssetResolver
35 from threading import Thread
35 from threading import Thread
36
36
37 from rhodecode.translation import _ as tsf
37 from rhodecode.translation import _ as tsf
38 from rhodecode.config.jsroutes import generate_jsroutes_content
38 from rhodecode.config.jsroutes import generate_jsroutes_content
39 from rhodecode.lib import auth
39 from rhodecode.lib import auth
40 from rhodecode.lib.base import get_auth_user
40 from rhodecode.lib.base import get_auth_user
41
41
42
42
43 import rhodecode
43 import rhodecode
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def add_renderer_globals(event):
49 def add_renderer_globals(event):
50 from rhodecode.lib import helpers
50 from rhodecode.lib import helpers
51
51
52 # TODO: When executed in pyramid view context the request is not available
52 # TODO: When executed in pyramid view context the request is not available
53 # in the event. Find a better solution to get the request.
53 # in the event. Find a better solution to get the request.
54 request = event['request'] or get_current_request()
54 request = event['request'] or get_current_request()
55
55
56 # Add Pyramid translation as '_' to context
56 # Add Pyramid translation as '_' to context
57 event['_'] = request.translate
57 event['_'] = request.translate
58 event['_ungettext'] = request.plularize
58 event['_ungettext'] = request.plularize
59 event['h'] = helpers
59 event['h'] = helpers
60
60
61
61
62 def add_localizer(event):
62 def add_localizer(event):
63 request = event.request
63 request = event.request
64 localizer = request.localizer
64 try:
65 localizer = request.localizer
66 except Exception:
67 log.exception('Failed to get localizer')
68 # NOTE(marcink): Handle bug in pyramid by malformed GET params could crash
69 # this resulting on various odd errors on missing translators
70 # see: https://github.com/Pylons/pyramid/issues/3399
71
72 def dummy_translate(*args, **kwargs):
73 return ''.join(args)
74 request.translate = tsf
75 request.plularize = dummy_translate
76 raise HTTPBadRequest()
65
77
66 def auto_translate(*args, **kwargs):
78 def auto_translate(*args, **kwargs):
67 return localizer.translate(tsf(*args, **kwargs))
79 return localizer.translate(tsf(*args, **kwargs))
68
80
69 request.translate = auto_translate
81 request.translate = auto_translate
70 request.plularize = localizer.pluralize
82 request.plularize = localizer.pluralize
71
83
72
84
73 def set_user_lang(event):
85 def set_user_lang(event):
74 request = event.request
86 request = event.request
75 cur_user = getattr(request, 'user', None)
87 cur_user = getattr(request, 'user', None)
76
88
77 if cur_user:
89 if cur_user:
78 user_lang = cur_user.get_instance().user_data.get('language')
90 user_lang = cur_user.get_instance().user_data.get('language')
79 if user_lang:
91 if user_lang:
80 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
92 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 event.request._LOCALE_ = user_lang
93 event.request._LOCALE_ = user_lang
82
94
83
95
84 def add_request_user_context(event):
96 def add_request_user_context(event):
85 """
97 """
86 Adds auth user into request context
98 Adds auth user into request context
87 """
99 """
88 request = event.request
100 request = event.request
89 # access req_id as soon as possible
101 # access req_id as soon as possible
90 req_id = request.req_id
102 req_id = request.req_id
91
103
92 if hasattr(request, 'vcs_call'):
104 if hasattr(request, 'vcs_call'):
93 # skip vcs calls
105 # skip vcs calls
94 return
106 return
95
107
96 if hasattr(request, 'rpc_method'):
108 if hasattr(request, 'rpc_method'):
97 # skip api calls
109 # skip api calls
98 return
110 return
99
111
100 auth_user = get_auth_user(request)
112 auth_user = get_auth_user(request)
101 request.user = auth_user
113 request.user = auth_user
102 request.environ['rc_auth_user'] = auth_user
114 request.environ['rc_auth_user'] = auth_user
103 request.environ['rc_auth_user_id'] = auth_user.user_id
115 request.environ['rc_auth_user_id'] = auth_user.user_id
104 request.environ['rc_req_id'] = req_id
116 request.environ['rc_req_id'] = req_id
105
117
106
118
107 def inject_app_settings(event):
119 def inject_app_settings(event):
108 settings = event.app.registry.settings
120 settings = event.app.registry.settings
109 # inject info about available permissions
121 # inject info about available permissions
110 auth.set_available_permissions(settings)
122 auth.set_available_permissions(settings)
111
123
112
124
113 def scan_repositories_if_enabled(event):
125 def scan_repositories_if_enabled(event):
114 """
126 """
115 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
127 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
116 does a repository scan if enabled in the settings.
128 does a repository scan if enabled in the settings.
117 """
129 """
118 settings = event.app.registry.settings
130 settings = event.app.registry.settings
119 vcs_server_enabled = settings['vcs.server.enable']
131 vcs_server_enabled = settings['vcs.server.enable']
120 import_on_startup = settings['startup.import_repos']
132 import_on_startup = settings['startup.import_repos']
121 if vcs_server_enabled and import_on_startup:
133 if vcs_server_enabled and import_on_startup:
122 from rhodecode.model.scm import ScmModel
134 from rhodecode.model.scm import ScmModel
123 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
135 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
124 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
136 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
125 repo2db_mapper(repositories, remove_obsolete=False)
137 repo2db_mapper(repositories, remove_obsolete=False)
126
138
127
139
128 def write_metadata_if_needed(event):
140 def write_metadata_if_needed(event):
129 """
141 """
130 Writes upgrade metadata
142 Writes upgrade metadata
131 """
143 """
132 import rhodecode
144 import rhodecode
133 from rhodecode.lib import system_info
145 from rhodecode.lib import system_info
134 from rhodecode.lib import ext_json
146 from rhodecode.lib import ext_json
135
147
136 fname = '.rcmetadata.json'
148 fname = '.rcmetadata.json'
137 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
149 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
138 metadata_destination = os.path.join(ini_loc, fname)
150 metadata_destination = os.path.join(ini_loc, fname)
139
151
140 def get_update_age():
152 def get_update_age():
141 now = datetime.datetime.utcnow()
153 now = datetime.datetime.utcnow()
142
154
143 with open(metadata_destination, 'rb') as f:
155 with open(metadata_destination, 'rb') as f:
144 data = ext_json.json.loads(f.read())
156 data = ext_json.json.loads(f.read())
145 if 'created_on' in data:
157 if 'created_on' in data:
146 update_date = parse(data['created_on'])
158 update_date = parse(data['created_on'])
147 diff = now - update_date
159 diff = now - update_date
148 return diff.total_seconds() / 60.0
160 return diff.total_seconds() / 60.0
149
161
150 return 0
162 return 0
151
163
152 def write():
164 def write():
153 configuration = system_info.SysInfo(
165 configuration = system_info.SysInfo(
154 system_info.rhodecode_config)()['value']
166 system_info.rhodecode_config)()['value']
155 license_token = configuration['config']['license_token']
167 license_token = configuration['config']['license_token']
156
168
157 setup = dict(
169 setup = dict(
158 workers=configuration['config']['server:main'].get(
170 workers=configuration['config']['server:main'].get(
159 'workers', '?'),
171 'workers', '?'),
160 worker_type=configuration['config']['server:main'].get(
172 worker_type=configuration['config']['server:main'].get(
161 'worker_class', 'sync'),
173 'worker_class', 'sync'),
162 )
174 )
163 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
175 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
164 del dbinfo['url']
176 del dbinfo['url']
165
177
166 metadata = dict(
178 metadata = dict(
167 desc='upgrade metadata info',
179 desc='upgrade metadata info',
168 license_token=license_token,
180 license_token=license_token,
169 created_on=datetime.datetime.utcnow().isoformat(),
181 created_on=datetime.datetime.utcnow().isoformat(),
170 usage=system_info.SysInfo(system_info.usage_info)()['value'],
182 usage=system_info.SysInfo(system_info.usage_info)()['value'],
171 platform=system_info.SysInfo(system_info.platform_type)()['value'],
183 platform=system_info.SysInfo(system_info.platform_type)()['value'],
172 database=dbinfo,
184 database=dbinfo,
173 cpu=system_info.SysInfo(system_info.cpu)()['value'],
185 cpu=system_info.SysInfo(system_info.cpu)()['value'],
174 memory=system_info.SysInfo(system_info.memory)()['value'],
186 memory=system_info.SysInfo(system_info.memory)()['value'],
175 setup=setup
187 setup=setup
176 )
188 )
177
189
178 with open(metadata_destination, 'wb') as f:
190 with open(metadata_destination, 'wb') as f:
179 f.write(ext_json.json.dumps(metadata))
191 f.write(ext_json.json.dumps(metadata))
180
192
181 settings = event.app.registry.settings
193 settings = event.app.registry.settings
182 if settings.get('metadata.skip'):
194 if settings.get('metadata.skip'):
183 return
195 return
184
196
185 # only write this every 24h, workers restart caused unwanted delays
197 # only write this every 24h, workers restart caused unwanted delays
186 try:
198 try:
187 age_in_min = get_update_age()
199 age_in_min = get_update_age()
188 except Exception:
200 except Exception:
189 age_in_min = 0
201 age_in_min = 0
190
202
191 if age_in_min > 60 * 60 * 24:
203 if age_in_min > 60 * 60 * 24:
192 return
204 return
193
205
194 try:
206 try:
195 write()
207 write()
196 except Exception:
208 except Exception:
197 pass
209 pass
198
210
199
211
200 def write_js_routes_if_enabled(event):
212 def write_js_routes_if_enabled(event):
201 registry = event.app.registry
213 registry = event.app.registry
202
214
203 mapper = registry.queryUtility(IRoutesMapper)
215 mapper = registry.queryUtility(IRoutesMapper)
204 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
216 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
205
217
206 def _extract_route_information(route):
218 def _extract_route_information(route):
207 """
219 """
208 Convert a route into tuple(name, path, args), eg:
220 Convert a route into tuple(name, path, args), eg:
209 ('show_user', '/profile/%(username)s', ['username'])
221 ('show_user', '/profile/%(username)s', ['username'])
210 """
222 """
211
223
212 routepath = route.pattern
224 routepath = route.pattern
213 pattern = route.pattern
225 pattern = route.pattern
214
226
215 def replace(matchobj):
227 def replace(matchobj):
216 if matchobj.group(1):
228 if matchobj.group(1):
217 return "%%(%s)s" % matchobj.group(1).split(':')[0]
229 return "%%(%s)s" % matchobj.group(1).split(':')[0]
218 else:
230 else:
219 return "%%(%s)s" % matchobj.group(2)
231 return "%%(%s)s" % matchobj.group(2)
220
232
221 routepath = _argument_prog.sub(replace, routepath)
233 routepath = _argument_prog.sub(replace, routepath)
222
234
223 if not routepath.startswith('/'):
235 if not routepath.startswith('/'):
224 routepath = '/'+routepath
236 routepath = '/'+routepath
225
237
226 return (
238 return (
227 route.name,
239 route.name,
228 routepath,
240 routepath,
229 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
241 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
230 for arg in _argument_prog.findall(pattern)]
242 for arg in _argument_prog.findall(pattern)]
231 )
243 )
232
244
233 def get_routes():
245 def get_routes():
234 # pyramid routes
246 # pyramid routes
235 for route in mapper.get_routes():
247 for route in mapper.get_routes():
236 if not route.name.startswith('__'):
248 if not route.name.startswith('__'):
237 yield _extract_route_information(route)
249 yield _extract_route_information(route)
238
250
239 if asbool(registry.settings.get('generate_js_files', 'false')):
251 if asbool(registry.settings.get('generate_js_files', 'false')):
240 static_path = AssetResolver().resolve('rhodecode:public').abspath()
252 static_path = AssetResolver().resolve('rhodecode:public').abspath()
241 jsroutes = get_routes()
253 jsroutes = get_routes()
242 jsroutes_file_content = generate_jsroutes_content(jsroutes)
254 jsroutes_file_content = generate_jsroutes_content(jsroutes)
243 jsroutes_file_path = os.path.join(
255 jsroutes_file_path = os.path.join(
244 static_path, 'js', 'rhodecode', 'routes.js')
256 static_path, 'js', 'rhodecode', 'routes.js')
245
257
246 try:
258 try:
247 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
259 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
248 f.write(jsroutes_file_content)
260 f.write(jsroutes_file_content)
249 except Exception:
261 except Exception:
250 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
262 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
251
263
252
264
253 class Subscriber(object):
265 class Subscriber(object):
254 """
266 """
255 Base class for subscribers to the pyramid event system.
267 Base class for subscribers to the pyramid event system.
256 """
268 """
257 def __call__(self, event):
269 def __call__(self, event):
258 self.run(event)
270 self.run(event)
259
271
260 def run(self, event):
272 def run(self, event):
261 raise NotImplementedError('Subclass has to implement this.')
273 raise NotImplementedError('Subclass has to implement this.')
262
274
263
275
264 class AsyncSubscriber(Subscriber):
276 class AsyncSubscriber(Subscriber):
265 """
277 """
266 Subscriber that handles the execution of events in a separate task to not
278 Subscriber that handles the execution of events in a separate task to not
267 block the execution of the code which triggers the event. It puts the
279 block the execution of the code which triggers the event. It puts the
268 received events into a queue from which the worker process takes them in
280 received events into a queue from which the worker process takes them in
269 order.
281 order.
270 """
282 """
271 def __init__(self):
283 def __init__(self):
272 self._stop = False
284 self._stop = False
273 self._eventq = Queue.Queue()
285 self._eventq = Queue.Queue()
274 self._worker = self.create_worker()
286 self._worker = self.create_worker()
275 self._worker.start()
287 self._worker.start()
276
288
277 def __call__(self, event):
289 def __call__(self, event):
278 self._eventq.put(event)
290 self._eventq.put(event)
279
291
280 def create_worker(self):
292 def create_worker(self):
281 worker = Thread(target=self.do_work)
293 worker = Thread(target=self.do_work)
282 worker.daemon = True
294 worker.daemon = True
283 return worker
295 return worker
284
296
285 def stop_worker(self):
297 def stop_worker(self):
286 self._stop = False
298 self._stop = False
287 self._eventq.put(None)
299 self._eventq.put(None)
288 self._worker.join()
300 self._worker.join()
289
301
290 def do_work(self):
302 def do_work(self):
291 while not self._stop:
303 while not self._stop:
292 event = self._eventq.get()
304 event = self._eventq.get()
293 if event is not None:
305 if event is not None:
294 self.run(event)
306 self.run(event)
295
307
296
308
297 class AsyncSubprocessSubscriber(AsyncSubscriber):
309 class AsyncSubprocessSubscriber(AsyncSubscriber):
298 """
310 """
299 Subscriber that uses the subprocess32 module to execute a command if an
311 Subscriber that uses the subprocess32 module to execute a command if an
300 event is received. Events are handled asynchronously.
312 event is received. Events are handled asynchronously.
301 """
313 """
302
314
303 def __init__(self, cmd, timeout=None):
315 def __init__(self, cmd, timeout=None):
304 super(AsyncSubprocessSubscriber, self).__init__()
316 super(AsyncSubprocessSubscriber, self).__init__()
305 self._cmd = cmd
317 self._cmd = cmd
306 self._timeout = timeout
318 self._timeout = timeout
307
319
308 def run(self, event):
320 def run(self, event):
309 cmd = self._cmd
321 cmd = self._cmd
310 timeout = self._timeout
322 timeout = self._timeout
311 log.debug('Executing command %s.', cmd)
323 log.debug('Executing command %s.', cmd)
312
324
313 try:
325 try:
314 output = subprocess32.check_output(
326 output = subprocess32.check_output(
315 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
327 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
316 log.debug('Command finished %s', cmd)
328 log.debug('Command finished %s', cmd)
317 if output:
329 if output:
318 log.debug('Command output: %s', output)
330 log.debug('Command output: %s', output)
319 except subprocess32.TimeoutExpired as e:
331 except subprocess32.TimeoutExpired as e:
320 log.exception('Timeout while executing command.')
332 log.exception('Timeout while executing command.')
321 if e.output:
333 if e.output:
322 log.error('Command output: %s', e.output)
334 log.error('Command output: %s', e.output)
323 except subprocess32.CalledProcessError as e:
335 except subprocess32.CalledProcessError as e:
324 log.exception('Error while executing command.')
336 log.exception('Error while executing command.')
325 if e.output:
337 if e.output:
326 log.error('Command output: %s', e.output)
338 log.error('Command output: %s', e.output)
327 except:
339 except:
328 log.exception(
340 log.exception(
329 'Exception while executing command %s.', cmd)
341 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now