##// END OF EJS Templates
core: improve attach context attribute to rely strictly on already provided user.
marcink -
r1776:e85e3fbe default
parent child Browse files
Show More
@@ -1,594 +1,592 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, ChangesetComment
59 from rhodecode.model.db import Repository, User, ChangesetComment
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 get_user_agent(environ):
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
166 return environ.get('HTTP_USER_AGENT')
167
167
168
168
169 def vcs_operation_context(
169 def vcs_operation_context(
170 environ, repo_name, username, action, scm, check_locking=True,
170 environ, repo_name, username, action, scm, check_locking=True,
171 is_shadow_repo=False):
171 is_shadow_repo=False):
172 """
172 """
173 Generate the context for a vcs operation, e.g. push or pull.
173 Generate the context for a vcs operation, e.g. push or pull.
174
174
175 This context is passed over the layers so that hooks triggered by the
175 This context is passed over the layers so that hooks triggered by the
176 vcs operation know details like the user, the user's IP address etc.
176 vcs operation know details like the user, the user's IP address etc.
177
177
178 :param check_locking: Allows to switch of the computation of the locking
178 :param check_locking: Allows to switch of the computation of the locking
179 data. This serves mainly the need of the simplevcs middleware to be
179 data. This serves mainly the need of the simplevcs middleware to be
180 able to disable this for certain operations.
180 able to disable this for certain operations.
181
181
182 """
182 """
183 # Tri-state value: False: unlock, None: nothing, True: lock
183 # Tri-state value: False: unlock, None: nothing, True: lock
184 make_lock = None
184 make_lock = None
185 locked_by = [None, None, None]
185 locked_by = [None, None, None]
186 is_anonymous = username == User.DEFAULT_USER
186 is_anonymous = username == User.DEFAULT_USER
187 if not is_anonymous and check_locking:
187 if not is_anonymous and check_locking:
188 log.debug('Checking locking on repository "%s"', repo_name)
188 log.debug('Checking locking on repository "%s"', repo_name)
189 user = User.get_by_username(username)
189 user = User.get_by_username(username)
190 repo = Repository.get_by_repo_name(repo_name)
190 repo = Repository.get_by_repo_name(repo_name)
191 make_lock, __, locked_by = repo.get_locking_state(
191 make_lock, __, locked_by = repo.get_locking_state(
192 action, user.user_id)
192 action, user.user_id)
193
193
194 settings_model = VcsSettingsModel(repo=repo_name)
194 settings_model = VcsSettingsModel(repo=repo_name)
195 ui_settings = settings_model.get_ui_settings()
195 ui_settings = settings_model.get_ui_settings()
196
196
197 extras = {
197 extras = {
198 'ip': get_ip_addr(environ),
198 'ip': get_ip_addr(environ),
199 'username': username,
199 'username': username,
200 'action': action,
200 'action': action,
201 'repository': repo_name,
201 'repository': repo_name,
202 'scm': scm,
202 'scm': scm,
203 'config': rhodecode.CONFIG['__file__'],
203 'config': rhodecode.CONFIG['__file__'],
204 'make_lock': make_lock,
204 'make_lock': make_lock,
205 'locked_by': locked_by,
205 'locked_by': locked_by,
206 'server_url': utils2.get_server_url(environ),
206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
207 'user_agent': get_user_agent(environ),
208 'hooks': get_enabled_hook_classes(ui_settings),
208 'hooks': get_enabled_hook_classes(ui_settings),
209 'is_shadow_repo': is_shadow_repo,
209 'is_shadow_repo': is_shadow_repo,
210 }
210 }
211 return extras
211 return extras
212
212
213
213
214 class BasicAuth(AuthBasicAuthenticator):
214 class BasicAuth(AuthBasicAuthenticator):
215
215
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
217 initial_call_detection=False, acl_repo_name=None):
217 initial_call_detection=False, acl_repo_name=None):
218 self.realm = realm
218 self.realm = realm
219 self.initial_call = initial_call_detection
219 self.initial_call = initial_call_detection
220 self.authfunc = authfunc
220 self.authfunc = authfunc
221 self.registry = registry
221 self.registry = registry
222 self.acl_repo_name = acl_repo_name
222 self.acl_repo_name = acl_repo_name
223 self._rc_auth_http_code = auth_http_code
223 self._rc_auth_http_code = auth_http_code
224
224
225 def _get_response_from_code(self, http_code):
225 def _get_response_from_code(self, http_code):
226 try:
226 try:
227 return get_exception(safe_int(http_code))
227 return get_exception(safe_int(http_code))
228 except Exception:
228 except Exception:
229 log.exception('Failed to fetch response for code %s' % http_code)
229 log.exception('Failed to fetch response for code %s' % http_code)
230 return HTTPForbidden
230 return HTTPForbidden
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 if self.authfunc(
254 if self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
257 return username
257 return username
258 if username and password:
258 if username and password:
259 # we mark that we actually executed authentication once, at
259 # we mark that we actually executed authentication once, at
260 # that point we can use the alternative auth code
260 # that point we can use the alternative auth code
261 self.initial_call = False
261 self.initial_call = False
262
262
263 return self.build_authentication()
263 return self.build_authentication()
264
264
265 __call__ = authenticate
265 __call__ = authenticate
266
266
267
267
268 def attach_context_attributes(context, request):
268 def attach_context_attributes(context, request, user_id):
269 """
269 """
270 Attach variables into template context called `c`, please note that
270 Attach variables into template context called `c`, please note that
271 request could be pylons or pyramid request in here.
271 request could be pylons or pyramid request in here.
272 """
272 """
273 rc_config = SettingsModel().get_all_settings(cache=True)
273 rc_config = SettingsModel().get_all_settings(cache=True)
274
274
275 context.rhodecode_version = rhodecode.__version__
275 context.rhodecode_version = rhodecode.__version__
276 context.rhodecode_edition = config.get('rhodecode.edition')
276 context.rhodecode_edition = config.get('rhodecode.edition')
277 # unique secret + version does not leak the version but keep consistency
277 # unique secret + version does not leak the version but keep consistency
278 context.rhodecode_version_hash = md5(
278 context.rhodecode_version_hash = md5(
279 config.get('beaker.session.secret', '') +
279 config.get('beaker.session.secret', '') +
280 rhodecode.__version__)[:8]
280 rhodecode.__version__)[:8]
281
281
282 # Default language set for the incoming request
282 # Default language set for the incoming request
283 context.language = translation.get_lang()[0]
283 context.language = translation.get_lang()[0]
284
284
285 # Visual options
285 # Visual options
286 context.visual = AttributeDict({})
286 context.visual = AttributeDict({})
287
287
288 # DB stored Visual Items
288 # DB stored Visual Items
289 context.visual.show_public_icon = str2bool(
289 context.visual.show_public_icon = str2bool(
290 rc_config.get('rhodecode_show_public_icon'))
290 rc_config.get('rhodecode_show_public_icon'))
291 context.visual.show_private_icon = str2bool(
291 context.visual.show_private_icon = str2bool(
292 rc_config.get('rhodecode_show_private_icon'))
292 rc_config.get('rhodecode_show_private_icon'))
293 context.visual.stylify_metatags = str2bool(
293 context.visual.stylify_metatags = str2bool(
294 rc_config.get('rhodecode_stylify_metatags'))
294 rc_config.get('rhodecode_stylify_metatags'))
295 context.visual.dashboard_items = safe_int(
295 context.visual.dashboard_items = safe_int(
296 rc_config.get('rhodecode_dashboard_items', 100))
296 rc_config.get('rhodecode_dashboard_items', 100))
297 context.visual.admin_grid_items = safe_int(
297 context.visual.admin_grid_items = safe_int(
298 rc_config.get('rhodecode_admin_grid_items', 100))
298 rc_config.get('rhodecode_admin_grid_items', 100))
299 context.visual.repository_fields = str2bool(
299 context.visual.repository_fields = str2bool(
300 rc_config.get('rhodecode_repository_fields'))
300 rc_config.get('rhodecode_repository_fields'))
301 context.visual.show_version = str2bool(
301 context.visual.show_version = str2bool(
302 rc_config.get('rhodecode_show_version'))
302 rc_config.get('rhodecode_show_version'))
303 context.visual.use_gravatar = str2bool(
303 context.visual.use_gravatar = str2bool(
304 rc_config.get('rhodecode_use_gravatar'))
304 rc_config.get('rhodecode_use_gravatar'))
305 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
305 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
306 context.visual.default_renderer = rc_config.get(
306 context.visual.default_renderer = rc_config.get(
307 'rhodecode_markup_renderer', 'rst')
307 'rhodecode_markup_renderer', 'rst')
308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
309 context.visual.rhodecode_support_url = \
309 context.visual.rhodecode_support_url = \
310 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
310 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
311
311
312 context.pre_code = rc_config.get('rhodecode_pre_code')
312 context.pre_code = rc_config.get('rhodecode_pre_code')
313 context.post_code = rc_config.get('rhodecode_post_code')
313 context.post_code = rc_config.get('rhodecode_post_code')
314 context.rhodecode_name = rc_config.get('rhodecode_title')
314 context.rhodecode_name = rc_config.get('rhodecode_title')
315 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
315 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
316 # if we have specified default_encoding in the request, it has more
316 # if we have specified default_encoding in the request, it has more
317 # priority
317 # priority
318 if request.GET.get('default_encoding'):
318 if request.GET.get('default_encoding'):
319 context.default_encodings.insert(0, request.GET.get('default_encoding'))
319 context.default_encodings.insert(0, request.GET.get('default_encoding'))
320 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
320 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
321
321
322 # INI stored
322 # INI stored
323 context.labs_active = str2bool(
323 context.labs_active = str2bool(
324 config.get('labs_settings_active', 'false'))
324 config.get('labs_settings_active', 'false'))
325 context.visual.allow_repo_location_change = str2bool(
325 context.visual.allow_repo_location_change = str2bool(
326 config.get('allow_repo_location_change', True))
326 config.get('allow_repo_location_change', True))
327 context.visual.allow_custom_hooks_settings = str2bool(
327 context.visual.allow_custom_hooks_settings = str2bool(
328 config.get('allow_custom_hooks_settings', True))
328 config.get('allow_custom_hooks_settings', True))
329 context.debug_style = str2bool(config.get('debug_style', False))
329 context.debug_style = str2bool(config.get('debug_style', False))
330
330
331 context.rhodecode_instanceid = config.get('instance_id')
331 context.rhodecode_instanceid = config.get('instance_id')
332
332
333 # AppEnlight
333 # AppEnlight
334 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
334 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
335 context.appenlight_api_public_key = config.get(
335 context.appenlight_api_public_key = config.get(
336 'appenlight.api_public_key', '')
336 'appenlight.api_public_key', '')
337 context.appenlight_server_url = config.get('appenlight.server_url', '')
337 context.appenlight_server_url = config.get('appenlight.server_url', '')
338
338
339 # JS template context
339 # JS template context
340 context.template_context = {
340 context.template_context = {
341 'repo_name': None,
341 'repo_name': None,
342 'repo_type': None,
342 'repo_type': None,
343 'repo_landing_commit': None,
343 'repo_landing_commit': None,
344 'rhodecode_user': {
344 'rhodecode_user': {
345 'username': None,
345 'username': None,
346 'email': None,
346 'email': None,
347 'notification_status': False
347 'notification_status': False
348 },
348 },
349 'visual': {
349 'visual': {
350 'default_renderer': None
350 'default_renderer': None
351 },
351 },
352 'commit_data': {
352 'commit_data': {
353 'commit_id': None
353 'commit_id': None
354 },
354 },
355 'pull_request_data': {'pull_request_id': None},
355 'pull_request_data': {'pull_request_id': None},
356 'timeago': {
356 'timeago': {
357 'refresh_time': 120 * 1000,
357 'refresh_time': 120 * 1000,
358 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
358 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
359 },
359 },
360 'pylons_dispatch': {
360 'pylons_dispatch': {
361 # 'controller': request.environ['pylons.routes_dict']['controller'],
361 # 'controller': request.environ['pylons.routes_dict']['controller'],
362 # 'action': request.environ['pylons.routes_dict']['action'],
362 # 'action': request.environ['pylons.routes_dict']['action'],
363 },
363 },
364 'pyramid_dispatch': {
364 'pyramid_dispatch': {
365
365
366 },
366 },
367 'extra': {'plugins': {}}
367 'extra': {'plugins': {}}
368 }
368 }
369 # END CONFIG VARS
369 # END CONFIG VARS
370
370
371 # TODO: This dosn't work when called from pylons compatibility tween.
371 # TODO: This dosn't work when called from pylons compatibility tween.
372 # Fix this and remove it from base controller.
372 # Fix this and remove it from base controller.
373 # context.repo_name = get_repo_slug(request) # can be empty
373 # context.repo_name = get_repo_slug(request) # can be empty
374
374
375 diffmode = 'sideside'
375 diffmode = 'sideside'
376 if request.GET.get('diffmode'):
376 if request.GET.get('diffmode'):
377 if request.GET['diffmode'] == 'unified':
377 if request.GET['diffmode'] == 'unified':
378 diffmode = 'unified'
378 diffmode = 'unified'
379 elif request.session.get('diffmode'):
379 elif request.session.get('diffmode'):
380 diffmode = request.session['diffmode']
380 diffmode = request.session['diffmode']
381
381
382 context.diffmode = diffmode
382 context.diffmode = diffmode
383
383
384 if request.session.get('diffmode') != diffmode:
384 if request.session.get('diffmode') != diffmode:
385 request.session['diffmode'] = diffmode
385 request.session['diffmode'] = diffmode
386
386
387 context.csrf_token = auth.get_csrf_token()
387 context.csrf_token = auth.get_csrf_token()
388 context.backends = rhodecode.BACKENDS.keys()
388 context.backends = rhodecode.BACKENDS.keys()
389 context.backends.sort()
389 context.backends.sort()
390 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
390 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
391 context.rhodecode_user.user_id)
392
393 context.pyramid_request = pyramid.threadlocal.get_current_request()
391 context.pyramid_request = pyramid.threadlocal.get_current_request()
394
392
395
393
396 def get_auth_user(environ):
394 def get_auth_user(environ):
397 ip_addr = get_ip_addr(environ)
395 ip_addr = get_ip_addr(environ)
398 # make sure that we update permissions each time we call controller
396 # make sure that we update permissions each time we call controller
399 _auth_token = (request.GET.get('auth_token', '') or
397 _auth_token = (request.GET.get('auth_token', '') or
400 request.GET.get('api_key', ''))
398 request.GET.get('api_key', ''))
401
399
402 if _auth_token:
400 if _auth_token:
403 # when using API_KEY we assume user exists, and
401 # when using API_KEY we assume user exists, and
404 # doesn't need auth based on cookies.
402 # doesn't need auth based on cookies.
405 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
403 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
406 authenticated = False
404 authenticated = False
407 else:
405 else:
408 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
406 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
409 try:
407 try:
410 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
408 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
411 ip_addr=ip_addr)
409 ip_addr=ip_addr)
412 except UserCreationError as e:
410 except UserCreationError as e:
413 h.flash(e, 'error')
411 h.flash(e, 'error')
414 # container auth or other auth functions that create users
412 # container auth or other auth functions that create users
415 # on the fly can throw this exception signaling that there's
413 # on the fly can throw this exception signaling that there's
416 # issue with user creation, explanation should be provided
414 # issue with user creation, explanation should be provided
417 # in Exception itself. We then create a simple blank
415 # in Exception itself. We then create a simple blank
418 # AuthUser
416 # AuthUser
419 auth_user = AuthUser(ip_addr=ip_addr)
417 auth_user = AuthUser(ip_addr=ip_addr)
420
418
421 if password_changed(auth_user, session):
419 if password_changed(auth_user, session):
422 session.invalidate()
420 session.invalidate()
423 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
421 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
424 auth_user = AuthUser(ip_addr=ip_addr)
422 auth_user = AuthUser(ip_addr=ip_addr)
425
423
426 authenticated = cookie_store.get('is_authenticated')
424 authenticated = cookie_store.get('is_authenticated')
427
425
428 if not auth_user.is_authenticated and auth_user.is_user_object:
426 if not auth_user.is_authenticated and auth_user.is_user_object:
429 # user is not authenticated and not empty
427 # user is not authenticated and not empty
430 auth_user.set_authenticated(authenticated)
428 auth_user.set_authenticated(authenticated)
431
429
432 return auth_user
430 return auth_user
433
431
434
432
435 class BaseController(WSGIController):
433 class BaseController(WSGIController):
436
434
437 def __before__(self):
435 def __before__(self):
438 """
436 """
439 __before__ is called before controller methods and after __call__
437 __before__ is called before controller methods and after __call__
440 """
438 """
441 # on each call propagate settings calls into global settings.
439 # on each call propagate settings calls into global settings.
442 set_rhodecode_config(config)
440 set_rhodecode_config(config)
443 attach_context_attributes(c, request)
441 attach_context_attributes(c, request, c.rhodecode_user.user_id)
444
442
445 # TODO: Remove this when fixed in attach_context_attributes()
443 # TODO: Remove this when fixed in attach_context_attributes()
446 c.repo_name = get_repo_slug(request) # can be empty
444 c.repo_name = get_repo_slug(request) # can be empty
447
445
448 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
446 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
449 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
447 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
450 self.sa = meta.Session
448 self.sa = meta.Session
451 self.scm_model = ScmModel(self.sa)
449 self.scm_model = ScmModel(self.sa)
452
450
453 # set user language
451 # set user language
454 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
452 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
455 if user_lang:
453 if user_lang:
456 translation.set_lang(user_lang)
454 translation.set_lang(user_lang)
457 log.debug('set language to %s for user %s',
455 log.debug('set language to %s for user %s',
458 user_lang, self._rhodecode_user)
456 user_lang, self._rhodecode_user)
459
457
460 def _dispatch_redirect(self, with_url, environ, start_response):
458 def _dispatch_redirect(self, with_url, environ, start_response):
461 resp = HTTPFound(with_url)
459 resp = HTTPFound(with_url)
462 environ['SCRIPT_NAME'] = '' # handle prefix middleware
460 environ['SCRIPT_NAME'] = '' # handle prefix middleware
463 environ['PATH_INFO'] = with_url
461 environ['PATH_INFO'] = with_url
464 return resp(environ, start_response)
462 return resp(environ, start_response)
465
463
466 def __call__(self, environ, start_response):
464 def __call__(self, environ, start_response):
467 """Invoke the Controller"""
465 """Invoke the Controller"""
468 # WSGIController.__call__ dispatches to the Controller method
466 # WSGIController.__call__ dispatches to the Controller method
469 # the request is routed to. This routing information is
467 # the request is routed to. This routing information is
470 # available in environ['pylons.routes_dict']
468 # available in environ['pylons.routes_dict']
471 from rhodecode.lib import helpers as h
469 from rhodecode.lib import helpers as h
472
470
473 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
471 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
474 if environ.get('debugtoolbar.wants_pylons_context', False):
472 if environ.get('debugtoolbar.wants_pylons_context', False):
475 environ['debugtoolbar.pylons_context'] = c._current_obj()
473 environ['debugtoolbar.pylons_context'] = c._current_obj()
476
474
477 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
475 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
478 environ['pylons.routes_dict']['action']])
476 environ['pylons.routes_dict']['action']])
479
477
480 self.rc_config = SettingsModel().get_all_settings(cache=True)
478 self.rc_config = SettingsModel().get_all_settings(cache=True)
481 self.ip_addr = get_ip_addr(environ)
479 self.ip_addr = get_ip_addr(environ)
482
480
483 # The rhodecode auth user is looked up and passed through the
481 # The rhodecode auth user is looked up and passed through the
484 # environ by the pylons compatibility tween in pyramid.
482 # environ by the pylons compatibility tween in pyramid.
485 # So we can just grab it from there.
483 # So we can just grab it from there.
486 auth_user = environ['rc_auth_user']
484 auth_user = environ['rc_auth_user']
487
485
488 # set globals for auth user
486 # set globals for auth user
489 request.user = auth_user
487 request.user = auth_user
490 c.rhodecode_user = self._rhodecode_user = auth_user
488 c.rhodecode_user = self._rhodecode_user = auth_user
491
489
492 log.info('IP: %s User: %s accessed %s [%s]' % (
490 log.info('IP: %s User: %s accessed %s [%s]' % (
493 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
491 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
494 _route_name)
492 _route_name)
495 )
493 )
496
494
497 user_obj = auth_user.get_instance()
495 user_obj = auth_user.get_instance()
498 if user_obj and user_obj.user_data.get('force_password_change'):
496 if user_obj and user_obj.user_data.get('force_password_change'):
499 h.flash('You are required to change your password', 'warning',
497 h.flash('You are required to change your password', 'warning',
500 ignore_duplicate=True)
498 ignore_duplicate=True)
501 return self._dispatch_redirect(
499 return self._dispatch_redirect(
502 url('my_account_password'), environ, start_response)
500 url('my_account_password'), environ, start_response)
503
501
504 return WSGIController.__call__(self, environ, start_response)
502 return WSGIController.__call__(self, environ, start_response)
505
503
506
504
507 class BaseRepoController(BaseController):
505 class BaseRepoController(BaseController):
508 """
506 """
509 Base class for controllers responsible for loading all needed data for
507 Base class for controllers responsible for loading all needed data for
510 repository loaded items are
508 repository loaded items are
511
509
512 c.rhodecode_repo: instance of scm repository
510 c.rhodecode_repo: instance of scm repository
513 c.rhodecode_db_repo: instance of db
511 c.rhodecode_db_repo: instance of db
514 c.repository_requirements_missing: shows that repository specific data
512 c.repository_requirements_missing: shows that repository specific data
515 could not be displayed due to the missing requirements
513 could not be displayed due to the missing requirements
516 c.repository_pull_requests: show number of open pull requests
514 c.repository_pull_requests: show number of open pull requests
517 """
515 """
518
516
519 def __before__(self):
517 def __before__(self):
520 super(BaseRepoController, self).__before__()
518 super(BaseRepoController, self).__before__()
521 if c.repo_name: # extracted from routes
519 if c.repo_name: # extracted from routes
522 db_repo = Repository.get_by_repo_name(c.repo_name)
520 db_repo = Repository.get_by_repo_name(c.repo_name)
523 if not db_repo:
521 if not db_repo:
524 return
522 return
525
523
526 log.debug(
524 log.debug(
527 'Found repository in database %s with state `%s`',
525 'Found repository in database %s with state `%s`',
528 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
526 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
529 route = getattr(request.environ.get('routes.route'), 'name', '')
527 route = getattr(request.environ.get('routes.route'), 'name', '')
530
528
531 # allow to delete repos that are somehow damages in filesystem
529 # allow to delete repos that are somehow damages in filesystem
532 if route in ['delete_repo']:
530 if route in ['delete_repo']:
533 return
531 return
534
532
535 if db_repo.repo_state in [Repository.STATE_PENDING]:
533 if db_repo.repo_state in [Repository.STATE_PENDING]:
536 if route in ['repo_creating_home']:
534 if route in ['repo_creating_home']:
537 return
535 return
538 check_url = url('repo_creating_home', repo_name=c.repo_name)
536 check_url = url('repo_creating_home', repo_name=c.repo_name)
539 return redirect(check_url)
537 return redirect(check_url)
540
538
541 self.rhodecode_db_repo = db_repo
539 self.rhodecode_db_repo = db_repo
542
540
543 missing_requirements = False
541 missing_requirements = False
544 try:
542 try:
545 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
543 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
546 except RepositoryRequirementError as e:
544 except RepositoryRequirementError as e:
547 missing_requirements = True
545 missing_requirements = True
548 self._handle_missing_requirements(e)
546 self._handle_missing_requirements(e)
549
547
550 if self.rhodecode_repo is None and not missing_requirements:
548 if self.rhodecode_repo is None and not missing_requirements:
551 log.error('%s this repository is present in database but it '
549 log.error('%s this repository is present in database but it '
552 'cannot be created as an scm instance', c.repo_name)
550 'cannot be created as an scm instance', c.repo_name)
553
551
554 h.flash(_(
552 h.flash(_(
555 "The repository at %(repo_name)s cannot be located.") %
553 "The repository at %(repo_name)s cannot be located.") %
556 {'repo_name': c.repo_name},
554 {'repo_name': c.repo_name},
557 category='error', ignore_duplicate=True)
555 category='error', ignore_duplicate=True)
558 redirect(h.route_path('home'))
556 redirect(h.route_path('home'))
559
557
560 # update last change according to VCS data
558 # update last change according to VCS data
561 if not missing_requirements:
559 if not missing_requirements:
562 commit = db_repo.get_commit(
560 commit = db_repo.get_commit(
563 pre_load=["author", "date", "message", "parents"])
561 pre_load=["author", "date", "message", "parents"])
564 db_repo.update_commit_cache(commit)
562 db_repo.update_commit_cache(commit)
565
563
566 # Prepare context
564 # Prepare context
567 c.rhodecode_db_repo = db_repo
565 c.rhodecode_db_repo = db_repo
568 c.rhodecode_repo = self.rhodecode_repo
566 c.rhodecode_repo = self.rhodecode_repo
569 c.repository_requirements_missing = missing_requirements
567 c.repository_requirements_missing = missing_requirements
570
568
571 self._update_global_counters(self.scm_model, db_repo)
569 self._update_global_counters(self.scm_model, db_repo)
572
570
573 def _update_global_counters(self, scm_model, db_repo):
571 def _update_global_counters(self, scm_model, db_repo):
574 """
572 """
575 Base variables that are exposed to every page of repository
573 Base variables that are exposed to every page of repository
576 """
574 """
577 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
575 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
578
576
579 def _handle_missing_requirements(self, error):
577 def _handle_missing_requirements(self, error):
580 self.rhodecode_repo = None
578 self.rhodecode_repo = None
581 log.error(
579 log.error(
582 'Requirements are missing for repository %s: %s',
580 'Requirements are missing for repository %s: %s',
583 c.repo_name, error.message)
581 c.repo_name, error.message)
584
582
585 summary_url = url('summary_home', repo_name=c.repo_name)
583 summary_url = url('summary_home', repo_name=c.repo_name)
586 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
584 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
587 settings_update_url = url('repo', repo_name=c.repo_name)
585 settings_update_url = url('repo', repo_name=c.repo_name)
588 path = request.path
586 path = request.path
589 should_redirect = (
587 should_redirect = (
590 path not in (summary_url, settings_update_url)
588 path not in (summary_url, settings_update_url)
591 and '/settings' not in path or path == statistics_url
589 and '/settings' not in path or path == statistics_url
592 )
590 )
593 if should_redirect:
591 if should_redirect:
594 redirect(summary_url)
592 redirect(summary_url)
@@ -1,312 +1,312 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 import io
20 import io
21 import re
21 import re
22 import datetime
22 import datetime
23 import logging
23 import logging
24 import pylons
24 import pylons
25 import Queue
25 import Queue
26 import subprocess32
26 import subprocess32
27 import os
27 import os
28
28
29 from pyramid.i18n import get_localizer
29 from pyramid.i18n import get_localizer
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from pyramid.interfaces import IRoutesMapper
31 from pyramid.interfaces import IRoutesMapper
32 from pyramid.settings import asbool
32 from pyramid.settings import asbool
33 from pyramid.path import AssetResolver
33 from pyramid.path import AssetResolver
34 from threading import Thread
34 from threading import Thread
35
35
36 from rhodecode.translation import _ as tsf
36 from rhodecode.translation import _ as tsf
37 from rhodecode.config.jsroutes import generate_jsroutes_content
37 from rhodecode.config.jsroutes import generate_jsroutes_content
38
38
39 import rhodecode
39 import rhodecode
40
40
41 from pylons.i18n.translation import _get_translator
41 from pylons.i18n.translation import _get_translator
42 from pylons.util import ContextObj
42 from pylons.util import ContextObj
43 from routes.util import URLGenerator
43 from routes.util import URLGenerator
44
44
45 from rhodecode.lib.base import attach_context_attributes, get_auth_user
45 from rhodecode.lib.base import attach_context_attributes, get_auth_user
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def add_renderer_globals(event):
50 def add_renderer_globals(event):
51 # Put pylons stuff into the context. This will be removed as soon as
51 # Put pylons stuff into the context. This will be removed as soon as
52 # migration to pyramid is finished.
52 # migration to pyramid is finished.
53 conf = pylons.config._current_obj()
53 conf = pylons.config._current_obj()
54 event['h'] = conf.get('pylons.h')
54 event['h'] = conf.get('pylons.h')
55 event['c'] = pylons.tmpl_context
55 event['c'] = pylons.tmpl_context
56 event['url'] = pylons.url
56 event['url'] = pylons.url
57
57
58 # TODO: When executed in pyramid view context the request is not available
58 # TODO: When executed in pyramid view context the request is not available
59 # in the event. Find a better solution to get the request.
59 # in the event. Find a better solution to get the request.
60 request = event['request'] or get_current_request()
60 request = event['request'] or get_current_request()
61
61
62 # Add Pyramid translation as '_' to context
62 # Add Pyramid translation as '_' to context
63 event['_'] = request.translate
63 event['_'] = request.translate
64 event['_ungettext'] = request.plularize
64 event['_ungettext'] = request.plularize
65
65
66
66
67 def add_localizer(event):
67 def add_localizer(event):
68 request = event.request
68 request = event.request
69 localizer = get_localizer(request)
69 localizer = get_localizer(request)
70
70
71 def auto_translate(*args, **kwargs):
71 def auto_translate(*args, **kwargs):
72 return localizer.translate(tsf(*args, **kwargs))
72 return localizer.translate(tsf(*args, **kwargs))
73
73
74 request.localizer = localizer
74 request.localizer = localizer
75 request.translate = auto_translate
75 request.translate = auto_translate
76 request.plularize = localizer.pluralize
76 request.plularize = localizer.pluralize
77
77
78
78
79 def set_user_lang(event):
79 def set_user_lang(event):
80 request = event.request
80 request = event.request
81 cur_user = getattr(request, 'user', None)
81 cur_user = getattr(request, 'user', None)
82
82
83 if cur_user:
83 if cur_user:
84 user_lang = cur_user.get_instance().user_data.get('language')
84 user_lang = cur_user.get_instance().user_data.get('language')
85 if user_lang:
85 if user_lang:
86 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
86 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
87 event.request._LOCALE_ = user_lang
87 event.request._LOCALE_ = user_lang
88
88
89
89
90 def add_pylons_context(event):
90 def add_pylons_context(event):
91 request = event.request
91 request = event.request
92
92
93 config = rhodecode.CONFIG
93 config = rhodecode.CONFIG
94 environ = request.environ
94 environ = request.environ
95 session = request.session
95 session = request.session
96
96
97 if hasattr(request, 'vcs_call'):
97 if hasattr(request, 'vcs_call'):
98 # skip vcs calls
98 # skip vcs calls
99 return
99 return
100
100
101 # Setup pylons globals.
101 # Setup pylons globals.
102 pylons.config._push_object(config)
102 pylons.config._push_object(config)
103 pylons.request._push_object(request)
103 pylons.request._push_object(request)
104 pylons.session._push_object(session)
104 pylons.session._push_object(session)
105 pylons.translator._push_object(_get_translator(config.get('lang')))
105 pylons.translator._push_object(_get_translator(config.get('lang')))
106
106
107 pylons.url._push_object(URLGenerator(config['routes.map'], environ))
107 pylons.url._push_object(URLGenerator(config['routes.map'], environ))
108 session_key = (
108 session_key = (
109 config['pylons.environ_config'].get('session', 'beaker.session'))
109 config['pylons.environ_config'].get('session', 'beaker.session'))
110 environ[session_key] = session
110 environ[session_key] = session
111
111
112 if hasattr(request, 'rpc_method'):
112 if hasattr(request, 'rpc_method'):
113 # skip api calls
113 # skip api calls
114 return
114 return
115
115
116 # Get the rhodecode auth user object and make it available.
116 # Get the rhodecode auth user object and make it available.
117 auth_user = get_auth_user(environ)
117 auth_user = get_auth_user(environ)
118 request.user = auth_user
118 request.user = auth_user
119 environ['rc_auth_user'] = auth_user
119 environ['rc_auth_user'] = auth_user
120
120
121 # Setup the pylons context object ('c')
121 # Setup the pylons context object ('c')
122 context = ContextObj()
122 context = ContextObj()
123 context.rhodecode_user = auth_user
123 context.rhodecode_user = auth_user
124 attach_context_attributes(context, request)
124 attach_context_attributes(context, request, request.user.user_id)
125 pylons.tmpl_context._push_object(context)
125 pylons.tmpl_context._push_object(context)
126
126
127
127
128 def scan_repositories_if_enabled(event):
128 def scan_repositories_if_enabled(event):
129 """
129 """
130 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
130 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
131 does a repository scan if enabled in the settings.
131 does a repository scan if enabled in the settings.
132 """
132 """
133 settings = event.app.registry.settings
133 settings = event.app.registry.settings
134 vcs_server_enabled = settings['vcs.server.enable']
134 vcs_server_enabled = settings['vcs.server.enable']
135 import_on_startup = settings['startup.import_repos']
135 import_on_startup = settings['startup.import_repos']
136 if vcs_server_enabled and import_on_startup:
136 if vcs_server_enabled and import_on_startup:
137 from rhodecode.model.scm import ScmModel
137 from rhodecode.model.scm import ScmModel
138 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
138 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
139 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
139 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
140 repo2db_mapper(repositories, remove_obsolete=False)
140 repo2db_mapper(repositories, remove_obsolete=False)
141
141
142
142
143 def write_metadata_if_needed(event):
143 def write_metadata_if_needed(event):
144 """
144 """
145 Writes upgrade metadata
145 Writes upgrade metadata
146 """
146 """
147 import rhodecode
147 import rhodecode
148 from rhodecode.lib import system_info
148 from rhodecode.lib import system_info
149 from rhodecode.lib import ext_json
149 from rhodecode.lib import ext_json
150
150
151 def write():
151 def write():
152 fname = '.rcmetadata.json'
152 fname = '.rcmetadata.json'
153 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
153 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
154 metadata_destination = os.path.join(ini_loc, fname)
154 metadata_destination = os.path.join(ini_loc, fname)
155
155
156 configuration = system_info.SysInfo(
156 configuration = system_info.SysInfo(
157 system_info.rhodecode_config)()['value']
157 system_info.rhodecode_config)()['value']
158 license_token = configuration['config']['license_token']
158 license_token = configuration['config']['license_token']
159 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
159 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 del dbinfo['url']
160 del dbinfo['url']
161 metadata = dict(
161 metadata = dict(
162 desc='upgrade metadata info',
162 desc='upgrade metadata info',
163 license_token=license_token,
163 license_token=license_token,
164 created_on=datetime.datetime.utcnow().isoformat(),
164 created_on=datetime.datetime.utcnow().isoformat(),
165 usage=system_info.SysInfo(system_info.usage_info)()['value'],
165 usage=system_info.SysInfo(system_info.usage_info)()['value'],
166 platform=system_info.SysInfo(system_info.platform_type)()['value'],
166 platform=system_info.SysInfo(system_info.platform_type)()['value'],
167 database=dbinfo,
167 database=dbinfo,
168 cpu=system_info.SysInfo(system_info.cpu)()['value'],
168 cpu=system_info.SysInfo(system_info.cpu)()['value'],
169 memory=system_info.SysInfo(system_info.memory)()['value'],
169 memory=system_info.SysInfo(system_info.memory)()['value'],
170 )
170 )
171
171
172 with open(metadata_destination, 'wb') as f:
172 with open(metadata_destination, 'wb') as f:
173 f.write(ext_json.json.dumps(metadata))
173 f.write(ext_json.json.dumps(metadata))
174
174
175 settings = event.app.registry.settings
175 settings = event.app.registry.settings
176 if settings.get('metadata.skip'):
176 if settings.get('metadata.skip'):
177 return
177 return
178
178
179 try:
179 try:
180 write()
180 write()
181 except Exception:
181 except Exception:
182 pass
182 pass
183
183
184
184
185 def write_js_routes_if_enabled(event):
185 def write_js_routes_if_enabled(event):
186 registry = event.app.registry
186 registry = event.app.registry
187
187
188 mapper = registry.queryUtility(IRoutesMapper)
188 mapper = registry.queryUtility(IRoutesMapper)
189 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
189 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
190
190
191 def _extract_route_information(route):
191 def _extract_route_information(route):
192 """
192 """
193 Convert a route into tuple(name, path, args), eg:
193 Convert a route into tuple(name, path, args), eg:
194 ('show_user', '/profile/%(username)s', ['username'])
194 ('show_user', '/profile/%(username)s', ['username'])
195 """
195 """
196
196
197 routepath = route.pattern
197 routepath = route.pattern
198 pattern = route.pattern
198 pattern = route.pattern
199
199
200 def replace(matchobj):
200 def replace(matchobj):
201 if matchobj.group(1):
201 if matchobj.group(1):
202 return "%%(%s)s" % matchobj.group(1).split(':')[0]
202 return "%%(%s)s" % matchobj.group(1).split(':')[0]
203 else:
203 else:
204 return "%%(%s)s" % matchobj.group(2)
204 return "%%(%s)s" % matchobj.group(2)
205
205
206 routepath = _argument_prog.sub(replace, routepath)
206 routepath = _argument_prog.sub(replace, routepath)
207
207
208 return (
208 return (
209 route.name,
209 route.name,
210 routepath,
210 routepath,
211 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
211 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
212 for arg in _argument_prog.findall(pattern)]
212 for arg in _argument_prog.findall(pattern)]
213 )
213 )
214
214
215 def get_routes():
215 def get_routes():
216 # pylons routes
216 # pylons routes
217 for route in rhodecode.CONFIG['routes.map'].jsroutes():
217 for route in rhodecode.CONFIG['routes.map'].jsroutes():
218 yield route
218 yield route
219
219
220 # pyramid routes
220 # pyramid routes
221 for route in mapper.get_routes():
221 for route in mapper.get_routes():
222 if not route.name.startswith('__'):
222 if not route.name.startswith('__'):
223 yield _extract_route_information(route)
223 yield _extract_route_information(route)
224
224
225 if asbool(registry.settings.get('generate_js_files', 'false')):
225 if asbool(registry.settings.get('generate_js_files', 'false')):
226 static_path = AssetResolver().resolve('rhodecode:public').abspath()
226 static_path = AssetResolver().resolve('rhodecode:public').abspath()
227 jsroutes = get_routes()
227 jsroutes = get_routes()
228 jsroutes_file_content = generate_jsroutes_content(jsroutes)
228 jsroutes_file_content = generate_jsroutes_content(jsroutes)
229 jsroutes_file_path = os.path.join(
229 jsroutes_file_path = os.path.join(
230 static_path, 'js', 'rhodecode', 'routes.js')
230 static_path, 'js', 'rhodecode', 'routes.js')
231
231
232 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
232 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
233 f.write(jsroutes_file_content)
233 f.write(jsroutes_file_content)
234
234
235
235
236 class Subscriber(object):
236 class Subscriber(object):
237 """
237 """
238 Base class for subscribers to the pyramid event system.
238 Base class for subscribers to the pyramid event system.
239 """
239 """
240 def __call__(self, event):
240 def __call__(self, event):
241 self.run(event)
241 self.run(event)
242
242
243 def run(self, event):
243 def run(self, event):
244 raise NotImplementedError('Subclass has to implement this.')
244 raise NotImplementedError('Subclass has to implement this.')
245
245
246
246
247 class AsyncSubscriber(Subscriber):
247 class AsyncSubscriber(Subscriber):
248 """
248 """
249 Subscriber that handles the execution of events in a separate task to not
249 Subscriber that handles the execution of events in a separate task to not
250 block the execution of the code which triggers the event. It puts the
250 block the execution of the code which triggers the event. It puts the
251 received events into a queue from which the worker process takes them in
251 received events into a queue from which the worker process takes them in
252 order.
252 order.
253 """
253 """
254 def __init__(self):
254 def __init__(self):
255 self._stop = False
255 self._stop = False
256 self._eventq = Queue.Queue()
256 self._eventq = Queue.Queue()
257 self._worker = self.create_worker()
257 self._worker = self.create_worker()
258 self._worker.start()
258 self._worker.start()
259
259
260 def __call__(self, event):
260 def __call__(self, event):
261 self._eventq.put(event)
261 self._eventq.put(event)
262
262
263 def create_worker(self):
263 def create_worker(self):
264 worker = Thread(target=self.do_work)
264 worker = Thread(target=self.do_work)
265 worker.daemon = True
265 worker.daemon = True
266 return worker
266 return worker
267
267
268 def stop_worker(self):
268 def stop_worker(self):
269 self._stop = False
269 self._stop = False
270 self._eventq.put(None)
270 self._eventq.put(None)
271 self._worker.join()
271 self._worker.join()
272
272
273 def do_work(self):
273 def do_work(self):
274 while not self._stop:
274 while not self._stop:
275 event = self._eventq.get()
275 event = self._eventq.get()
276 if event is not None:
276 if event is not None:
277 self.run(event)
277 self.run(event)
278
278
279
279
280 class AsyncSubprocessSubscriber(AsyncSubscriber):
280 class AsyncSubprocessSubscriber(AsyncSubscriber):
281 """
281 """
282 Subscriber that uses the subprocess32 module to execute a command if an
282 Subscriber that uses the subprocess32 module to execute a command if an
283 event is received. Events are handled asynchronously.
283 event is received. Events are handled asynchronously.
284 """
284 """
285
285
286 def __init__(self, cmd, timeout=None):
286 def __init__(self, cmd, timeout=None):
287 super(AsyncSubprocessSubscriber, self).__init__()
287 super(AsyncSubprocessSubscriber, self).__init__()
288 self._cmd = cmd
288 self._cmd = cmd
289 self._timeout = timeout
289 self._timeout = timeout
290
290
291 def run(self, event):
291 def run(self, event):
292 cmd = self._cmd
292 cmd = self._cmd
293 timeout = self._timeout
293 timeout = self._timeout
294 log.debug('Executing command %s.', cmd)
294 log.debug('Executing command %s.', cmd)
295
295
296 try:
296 try:
297 output = subprocess32.check_output(
297 output = subprocess32.check_output(
298 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
298 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
299 log.debug('Command finished %s', cmd)
299 log.debug('Command finished %s', cmd)
300 if output:
300 if output:
301 log.debug('Command output: %s', output)
301 log.debug('Command output: %s', output)
302 except subprocess32.TimeoutExpired as e:
302 except subprocess32.TimeoutExpired as e:
303 log.exception('Timeout while executing command.')
303 log.exception('Timeout while executing command.')
304 if e.output:
304 if e.output:
305 log.error('Command output: %s', e.output)
305 log.error('Command output: %s', e.output)
306 except subprocess32.CalledProcessError as e:
306 except subprocess32.CalledProcessError as e:
307 log.exception('Error while executing command.')
307 log.exception('Error while executing command.')
308 if e.output:
308 if e.output:
309 log.error('Command output: %s', e.output)
309 log.error('Command output: %s', e.output)
310 except:
310 except:
311 log.exception(
311 log.exception(
312 'Exception while executing command %s.', cmd)
312 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now