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