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