##// END OF EJS Templates
tests: add require methods to testing request....
marcink -
r2317:830b678e default
parent child Browse files
Show More
@@ -1,649 +1,664 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 markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32 import pyramid.threadlocal
32 import pyramid.threadlocal
33
33
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
45 get_repo_slug, set_rhodecode_config, password_changed,
45 get_repo_slug, set_rhodecode_config, password_changed,
46 get_enabled_hook_classes)
46 get_enabled_hook_classes)
47 from rhodecode.lib.utils2 import (
47 from rhodecode.lib.utils2 import (
48 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str)
48 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str)
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.db import Repository, User, ChangesetComment
50 from rhodecode.model.db import Repository, User, ChangesetComment
51 from rhodecode.model.notification import NotificationModel
51 from rhodecode.model.notification import NotificationModel
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
53 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
54
54
55 # NOTE(marcink): remove after base controller is no longer required
55 # NOTE(marcink): remove after base controller is no longer required
56 from pylons.controllers import WSGIController
56 from pylons.controllers import WSGIController
57 from pylons.i18n import translation
57 from pylons.i18n import translation
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 # hack to make the migration to pyramid easier
62 # hack to make the migration to pyramid easier
63 def render(template_name, extra_vars=None, cache_key=None,
63 def render(template_name, extra_vars=None, cache_key=None,
64 cache_type=None, cache_expire=None):
64 cache_type=None, cache_expire=None):
65 """Render a template with Mako
65 """Render a template with Mako
66
66
67 Accepts the cache options ``cache_key``, ``cache_type``, and
67 Accepts the cache options ``cache_key``, ``cache_type``, and
68 ``cache_expire``.
68 ``cache_expire``.
69
69
70 """
70 """
71 from pylons.templating import literal
71 from pylons.templating import literal
72 from pylons.templating import cached_template, pylons_globals
72 from pylons.templating import cached_template, pylons_globals
73
73
74 # Create a render callable for the cache function
74 # Create a render callable for the cache function
75 def render_template():
75 def render_template():
76 # Pull in extra vars if needed
76 # Pull in extra vars if needed
77 globs = extra_vars or {}
77 globs = extra_vars or {}
78
78
79 # Second, get the globals
79 # Second, get the globals
80 globs.update(pylons_globals())
80 globs.update(pylons_globals())
81
81
82 globs['_ungettext'] = globs['ungettext']
82 globs['_ungettext'] = globs['ungettext']
83 # Grab a template reference
83 # Grab a template reference
84 template = globs['app_globals'].mako_lookup.get_template(template_name)
84 template = globs['app_globals'].mako_lookup.get_template(template_name)
85
85
86 return literal(template.render_unicode(**globs))
86 return literal(template.render_unicode(**globs))
87
87
88 return cached_template(template_name, render_template, cache_key=cache_key,
88 return cached_template(template_name, render_template, cache_key=cache_key,
89 cache_type=cache_type, cache_expire=cache_expire)
89 cache_type=cache_type, cache_expire=cache_expire)
90
90
91 def _filter_proxy(ip):
91 def _filter_proxy(ip):
92 """
92 """
93 Passed in IP addresses in HEADERS can be in a special format of multiple
93 Passed in IP addresses in HEADERS can be in a special format of multiple
94 ips. Those comma separated IPs are passed from various proxies in the
94 ips. Those comma separated IPs are passed from various proxies in the
95 chain of request processing. The left-most being the original client.
95 chain of request processing. The left-most being the original client.
96 We only care about the first IP which came from the org. client.
96 We only care about the first IP which came from the org. client.
97
97
98 :param ip: ip string from headers
98 :param ip: ip string from headers
99 """
99 """
100 if ',' in ip:
100 if ',' in ip:
101 _ips = ip.split(',')
101 _ips = ip.split(',')
102 _first_ip = _ips[0].strip()
102 _first_ip = _ips[0].strip()
103 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
103 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
104 return _first_ip
104 return _first_ip
105 return ip
105 return ip
106
106
107
107
108 def _filter_port(ip):
108 def _filter_port(ip):
109 """
109 """
110 Removes a port from ip, there are 4 main cases to handle here.
110 Removes a port from ip, there are 4 main cases to handle here.
111 - ipv4 eg. 127.0.0.1
111 - ipv4 eg. 127.0.0.1
112 - ipv6 eg. ::1
112 - ipv6 eg. ::1
113 - ipv4+port eg. 127.0.0.1:8080
113 - ipv4+port eg. 127.0.0.1:8080
114 - ipv6+port eg. [::1]:8080
114 - ipv6+port eg. [::1]:8080
115
115
116 :param ip:
116 :param ip:
117 """
117 """
118 def is_ipv6(ip_addr):
118 def is_ipv6(ip_addr):
119 if hasattr(socket, 'inet_pton'):
119 if hasattr(socket, 'inet_pton'):
120 try:
120 try:
121 socket.inet_pton(socket.AF_INET6, ip_addr)
121 socket.inet_pton(socket.AF_INET6, ip_addr)
122 except socket.error:
122 except socket.error:
123 return False
123 return False
124 else:
124 else:
125 # fallback to ipaddress
125 # fallback to ipaddress
126 try:
126 try:
127 ipaddress.IPv6Address(safe_unicode(ip_addr))
127 ipaddress.IPv6Address(safe_unicode(ip_addr))
128 except Exception:
128 except Exception:
129 return False
129 return False
130 return True
130 return True
131
131
132 if ':' not in ip: # must be ipv4 pure ip
132 if ':' not in ip: # must be ipv4 pure ip
133 return ip
133 return ip
134
134
135 if '[' in ip and ']' in ip: # ipv6 with port
135 if '[' in ip and ']' in ip: # ipv6 with port
136 return ip.split(']')[0][1:].lower()
136 return ip.split(']')[0][1:].lower()
137
137
138 # must be ipv6 or ipv4 with port
138 # must be ipv6 or ipv4 with port
139 if is_ipv6(ip):
139 if is_ipv6(ip):
140 return ip
140 return ip
141 else:
141 else:
142 ip, _port = ip.split(':')[:2] # means ipv4+port
142 ip, _port = ip.split(':')[:2] # means ipv4+port
143 return ip
143 return ip
144
144
145
145
146 def get_ip_addr(environ):
146 def get_ip_addr(environ):
147 proxy_key = 'HTTP_X_REAL_IP'
147 proxy_key = 'HTTP_X_REAL_IP'
148 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
148 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
149 def_key = 'REMOTE_ADDR'
149 def_key = 'REMOTE_ADDR'
150 _filters = lambda x: _filter_port(_filter_proxy(x))
150 _filters = lambda x: _filter_port(_filter_proxy(x))
151
151
152 ip = environ.get(proxy_key)
152 ip = environ.get(proxy_key)
153 if ip:
153 if ip:
154 return _filters(ip)
154 return _filters(ip)
155
155
156 ip = environ.get(proxy_key2)
156 ip = environ.get(proxy_key2)
157 if ip:
157 if ip:
158 return _filters(ip)
158 return _filters(ip)
159
159
160 ip = environ.get(def_key, '0.0.0.0')
160 ip = environ.get(def_key, '0.0.0.0')
161 return _filters(ip)
161 return _filters(ip)
162
162
163
163
164 def get_server_ip_addr(environ, log_errors=True):
164 def get_server_ip_addr(environ, log_errors=True):
165 hostname = environ.get('SERVER_NAME')
165 hostname = environ.get('SERVER_NAME')
166 try:
166 try:
167 return socket.gethostbyname(hostname)
167 return socket.gethostbyname(hostname)
168 except Exception as e:
168 except Exception as e:
169 if log_errors:
169 if log_errors:
170 # in some cases this lookup is not possible, and we don't want to
170 # in some cases this lookup is not possible, and we don't want to
171 # make it an exception in logs
171 # make it an exception in logs
172 log.exception('Could not retrieve server ip address: %s', e)
172 log.exception('Could not retrieve server ip address: %s', e)
173 return hostname
173 return hostname
174
174
175
175
176 def get_server_port(environ):
176 def get_server_port(environ):
177 return environ.get('SERVER_PORT')
177 return environ.get('SERVER_PORT')
178
178
179
179
180 def get_access_path(environ):
180 def get_access_path(environ):
181 path = environ.get('PATH_INFO')
181 path = environ.get('PATH_INFO')
182 org_req = environ.get('pylons.original_request')
182 org_req = environ.get('pylons.original_request')
183 if org_req:
183 if org_req:
184 path = org_req.environ.get('PATH_INFO')
184 path = org_req.environ.get('PATH_INFO')
185 return path
185 return path
186
186
187
187
188 def get_user_agent(environ):
188 def get_user_agent(environ):
189 return environ.get('HTTP_USER_AGENT')
189 return environ.get('HTTP_USER_AGENT')
190
190
191
191
192 def vcs_operation_context(
192 def vcs_operation_context(
193 environ, repo_name, username, action, scm, check_locking=True,
193 environ, repo_name, username, action, scm, check_locking=True,
194 is_shadow_repo=False):
194 is_shadow_repo=False):
195 """
195 """
196 Generate the context for a vcs operation, e.g. push or pull.
196 Generate the context for a vcs operation, e.g. push or pull.
197
197
198 This context is passed over the layers so that hooks triggered by the
198 This context is passed over the layers so that hooks triggered by the
199 vcs operation know details like the user, the user's IP address etc.
199 vcs operation know details like the user, the user's IP address etc.
200
200
201 :param check_locking: Allows to switch of the computation of the locking
201 :param check_locking: Allows to switch of the computation of the locking
202 data. This serves mainly the need of the simplevcs middleware to be
202 data. This serves mainly the need of the simplevcs middleware to be
203 able to disable this for certain operations.
203 able to disable this for certain operations.
204
204
205 """
205 """
206 # Tri-state value: False: unlock, None: nothing, True: lock
206 # Tri-state value: False: unlock, None: nothing, True: lock
207 make_lock = None
207 make_lock = None
208 locked_by = [None, None, None]
208 locked_by = [None, None, None]
209 is_anonymous = username == User.DEFAULT_USER
209 is_anonymous = username == User.DEFAULT_USER
210 if not is_anonymous and check_locking:
210 if not is_anonymous and check_locking:
211 log.debug('Checking locking on repository "%s"', repo_name)
211 log.debug('Checking locking on repository "%s"', repo_name)
212 user = User.get_by_username(username)
212 user = User.get_by_username(username)
213 repo = Repository.get_by_repo_name(repo_name)
213 repo = Repository.get_by_repo_name(repo_name)
214 make_lock, __, locked_by = repo.get_locking_state(
214 make_lock, __, locked_by = repo.get_locking_state(
215 action, user.user_id)
215 action, user.user_id)
216
216
217 settings_model = VcsSettingsModel(repo=repo_name)
217 settings_model = VcsSettingsModel(repo=repo_name)
218 ui_settings = settings_model.get_ui_settings()
218 ui_settings = settings_model.get_ui_settings()
219
219
220 extras = {
220 extras = {
221 'ip': get_ip_addr(environ),
221 'ip': get_ip_addr(environ),
222 'username': username,
222 'username': username,
223 'action': action,
223 'action': action,
224 'repository': repo_name,
224 'repository': repo_name,
225 'scm': scm,
225 'scm': scm,
226 'config': rhodecode.CONFIG['__file__'],
226 'config': rhodecode.CONFIG['__file__'],
227 'make_lock': make_lock,
227 'make_lock': make_lock,
228 'locked_by': locked_by,
228 'locked_by': locked_by,
229 'server_url': utils2.get_server_url(environ),
229 'server_url': utils2.get_server_url(environ),
230 'user_agent': get_user_agent(environ),
230 'user_agent': get_user_agent(environ),
231 'hooks': get_enabled_hook_classes(ui_settings),
231 'hooks': get_enabled_hook_classes(ui_settings),
232 'is_shadow_repo': is_shadow_repo,
232 'is_shadow_repo': is_shadow_repo,
233 }
233 }
234 return extras
234 return extras
235
235
236
236
237 class BasicAuth(AuthBasicAuthenticator):
237 class BasicAuth(AuthBasicAuthenticator):
238
238
239 def __init__(self, realm, authfunc, registry, auth_http_code=None,
239 def __init__(self, realm, authfunc, registry, auth_http_code=None,
240 initial_call_detection=False, acl_repo_name=None):
240 initial_call_detection=False, acl_repo_name=None):
241 self.realm = realm
241 self.realm = realm
242 self.initial_call = initial_call_detection
242 self.initial_call = initial_call_detection
243 self.authfunc = authfunc
243 self.authfunc = authfunc
244 self.registry = registry
244 self.registry = registry
245 self.acl_repo_name = acl_repo_name
245 self.acl_repo_name = acl_repo_name
246 self._rc_auth_http_code = auth_http_code
246 self._rc_auth_http_code = auth_http_code
247
247
248 def _get_response_from_code(self, http_code):
248 def _get_response_from_code(self, http_code):
249 try:
249 try:
250 return get_exception(safe_int(http_code))
250 return get_exception(safe_int(http_code))
251 except Exception:
251 except Exception:
252 log.exception('Failed to fetch response for code %s' % http_code)
252 log.exception('Failed to fetch response for code %s' % http_code)
253 return HTTPForbidden
253 return HTTPForbidden
254
254
255 def get_rc_realm(self):
255 def get_rc_realm(self):
256 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
256 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
257
257
258 def build_authentication(self):
258 def build_authentication(self):
259 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
259 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
260 if self._rc_auth_http_code and not self.initial_call:
260 if self._rc_auth_http_code and not self.initial_call:
261 # return alternative HTTP code if alternative http return code
261 # return alternative HTTP code if alternative http return code
262 # is specified in RhodeCode config, but ONLY if it's not the
262 # is specified in RhodeCode config, but ONLY if it's not the
263 # FIRST call
263 # FIRST call
264 custom_response_klass = self._get_response_from_code(
264 custom_response_klass = self._get_response_from_code(
265 self._rc_auth_http_code)
265 self._rc_auth_http_code)
266 return custom_response_klass(headers=head)
266 return custom_response_klass(headers=head)
267 return HTTPUnauthorized(headers=head)
267 return HTTPUnauthorized(headers=head)
268
268
269 def authenticate(self, environ):
269 def authenticate(self, environ):
270 authorization = AUTHORIZATION(environ)
270 authorization = AUTHORIZATION(environ)
271 if not authorization:
271 if not authorization:
272 return self.build_authentication()
272 return self.build_authentication()
273 (authmeth, auth) = authorization.split(' ', 1)
273 (authmeth, auth) = authorization.split(' ', 1)
274 if 'basic' != authmeth.lower():
274 if 'basic' != authmeth.lower():
275 return self.build_authentication()
275 return self.build_authentication()
276 auth = auth.strip().decode('base64')
276 auth = auth.strip().decode('base64')
277 _parts = auth.split(':', 1)
277 _parts = auth.split(':', 1)
278 if len(_parts) == 2:
278 if len(_parts) == 2:
279 username, password = _parts
279 username, password = _parts
280 auth_data = self.authfunc(
280 auth_data = self.authfunc(
281 username, password, environ, VCS_TYPE,
281 username, password, environ, VCS_TYPE,
282 registry=self.registry, acl_repo_name=self.acl_repo_name)
282 registry=self.registry, acl_repo_name=self.acl_repo_name)
283 if auth_data:
283 if auth_data:
284 return {'username': username, 'auth_data': auth_data}
284 return {'username': username, 'auth_data': auth_data}
285 if username and password:
285 if username and password:
286 # we mark that we actually executed authentication once, at
286 # we mark that we actually executed authentication once, at
287 # that point we can use the alternative auth code
287 # that point we can use the alternative auth code
288 self.initial_call = False
288 self.initial_call = False
289
289
290 return self.build_authentication()
290 return self.build_authentication()
291
291
292 __call__ = authenticate
292 __call__ = authenticate
293
293
294
294
295 def calculate_version_hash(config):
295 def calculate_version_hash(config):
296 return md5(
296 return md5(
297 config.get('beaker.session.secret', '') +
297 config.get('beaker.session.secret', '') +
298 rhodecode.__version__)[:8]
298 rhodecode.__version__)[:8]
299
299
300
300
301 def get_current_lang(request):
301 def get_current_lang(request):
302 # NOTE(marcink): remove after pyramid move
302 # NOTE(marcink): remove after pyramid move
303 try:
303 try:
304 return translation.get_lang()[0]
304 return translation.get_lang()[0]
305 except:
305 except:
306 pass
306 pass
307
307
308 return getattr(request, '_LOCALE_', request.locale_name)
308 return getattr(request, '_LOCALE_', request.locale_name)
309
309
310
310
311 def attach_context_attributes(context, request, user_id):
311 def attach_context_attributes(context, request, user_id):
312 """
312 """
313 Attach variables into template context called `c`, please note that
313 Attach variables into template context called `c`, please note that
314 request could be pylons or pyramid request in here.
314 request could be pylons or pyramid request in here.
315 """
315 """
316 # NOTE(marcink): remove check after pyramid migration
316 # NOTE(marcink): remove check after pyramid migration
317 if hasattr(request, 'registry'):
317 if hasattr(request, 'registry'):
318 config = request.registry.settings
318 config = request.registry.settings
319 else:
319 else:
320 from pylons import config
320 from pylons import config
321
321
322 rc_config = SettingsModel().get_all_settings(cache=True)
322 rc_config = SettingsModel().get_all_settings(cache=True)
323
323
324 context.rhodecode_version = rhodecode.__version__
324 context.rhodecode_version = rhodecode.__version__
325 context.rhodecode_edition = config.get('rhodecode.edition')
325 context.rhodecode_edition = config.get('rhodecode.edition')
326 # unique secret + version does not leak the version but keep consistency
326 # unique secret + version does not leak the version but keep consistency
327 context.rhodecode_version_hash = calculate_version_hash(config)
327 context.rhodecode_version_hash = calculate_version_hash(config)
328
328
329 # Default language set for the incoming request
329 # Default language set for the incoming request
330 context.language = get_current_lang(request)
330 context.language = get_current_lang(request)
331
331
332 # Visual options
332 # Visual options
333 context.visual = AttributeDict({})
333 context.visual = AttributeDict({})
334
334
335 # DB stored Visual Items
335 # DB stored Visual Items
336 context.visual.show_public_icon = str2bool(
336 context.visual.show_public_icon = str2bool(
337 rc_config.get('rhodecode_show_public_icon'))
337 rc_config.get('rhodecode_show_public_icon'))
338 context.visual.show_private_icon = str2bool(
338 context.visual.show_private_icon = str2bool(
339 rc_config.get('rhodecode_show_private_icon'))
339 rc_config.get('rhodecode_show_private_icon'))
340 context.visual.stylify_metatags = str2bool(
340 context.visual.stylify_metatags = str2bool(
341 rc_config.get('rhodecode_stylify_metatags'))
341 rc_config.get('rhodecode_stylify_metatags'))
342 context.visual.dashboard_items = safe_int(
342 context.visual.dashboard_items = safe_int(
343 rc_config.get('rhodecode_dashboard_items', 100))
343 rc_config.get('rhodecode_dashboard_items', 100))
344 context.visual.admin_grid_items = safe_int(
344 context.visual.admin_grid_items = safe_int(
345 rc_config.get('rhodecode_admin_grid_items', 100))
345 rc_config.get('rhodecode_admin_grid_items', 100))
346 context.visual.repository_fields = str2bool(
346 context.visual.repository_fields = str2bool(
347 rc_config.get('rhodecode_repository_fields'))
347 rc_config.get('rhodecode_repository_fields'))
348 context.visual.show_version = str2bool(
348 context.visual.show_version = str2bool(
349 rc_config.get('rhodecode_show_version'))
349 rc_config.get('rhodecode_show_version'))
350 context.visual.use_gravatar = str2bool(
350 context.visual.use_gravatar = str2bool(
351 rc_config.get('rhodecode_use_gravatar'))
351 rc_config.get('rhodecode_use_gravatar'))
352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
353 context.visual.default_renderer = rc_config.get(
353 context.visual.default_renderer = rc_config.get(
354 'rhodecode_markup_renderer', 'rst')
354 'rhodecode_markup_renderer', 'rst')
355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
356 context.visual.rhodecode_support_url = \
356 context.visual.rhodecode_support_url = \
357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
358
358
359 context.visual.affected_files_cut_off = 60
359 context.visual.affected_files_cut_off = 60
360
360
361 context.pre_code = rc_config.get('rhodecode_pre_code')
361 context.pre_code = rc_config.get('rhodecode_pre_code')
362 context.post_code = rc_config.get('rhodecode_post_code')
362 context.post_code = rc_config.get('rhodecode_post_code')
363 context.rhodecode_name = rc_config.get('rhodecode_title')
363 context.rhodecode_name = rc_config.get('rhodecode_title')
364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
365 # if we have specified default_encoding in the request, it has more
365 # if we have specified default_encoding in the request, it has more
366 # priority
366 # priority
367 if request.GET.get('default_encoding'):
367 if request.GET.get('default_encoding'):
368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
370
370
371 # INI stored
371 # INI stored
372 context.labs_active = str2bool(
372 context.labs_active = str2bool(
373 config.get('labs_settings_active', 'false'))
373 config.get('labs_settings_active', 'false'))
374 context.visual.allow_repo_location_change = str2bool(
374 context.visual.allow_repo_location_change = str2bool(
375 config.get('allow_repo_location_change', True))
375 config.get('allow_repo_location_change', True))
376 context.visual.allow_custom_hooks_settings = str2bool(
376 context.visual.allow_custom_hooks_settings = str2bool(
377 config.get('allow_custom_hooks_settings', True))
377 config.get('allow_custom_hooks_settings', True))
378 context.debug_style = str2bool(config.get('debug_style', False))
378 context.debug_style = str2bool(config.get('debug_style', False))
379
379
380 context.rhodecode_instanceid = config.get('instance_id')
380 context.rhodecode_instanceid = config.get('instance_id')
381
381
382 context.visual.cut_off_limit_diff = safe_int(
382 context.visual.cut_off_limit_diff = safe_int(
383 config.get('cut_off_limit_diff'))
383 config.get('cut_off_limit_diff'))
384 context.visual.cut_off_limit_file = safe_int(
384 context.visual.cut_off_limit_file = safe_int(
385 config.get('cut_off_limit_file'))
385 config.get('cut_off_limit_file'))
386
386
387 # AppEnlight
387 # AppEnlight
388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
389 context.appenlight_api_public_key = config.get(
389 context.appenlight_api_public_key = config.get(
390 'appenlight.api_public_key', '')
390 'appenlight.api_public_key', '')
391 context.appenlight_server_url = config.get('appenlight.server_url', '')
391 context.appenlight_server_url = config.get('appenlight.server_url', '')
392
392
393 # JS template context
393 # JS template context
394 context.template_context = {
394 context.template_context = {
395 'repo_name': None,
395 'repo_name': None,
396 'repo_type': None,
396 'repo_type': None,
397 'repo_landing_commit': None,
397 'repo_landing_commit': None,
398 'rhodecode_user': {
398 'rhodecode_user': {
399 'username': None,
399 'username': None,
400 'email': None,
400 'email': None,
401 'notification_status': False
401 'notification_status': False
402 },
402 },
403 'visual': {
403 'visual': {
404 'default_renderer': None
404 'default_renderer': None
405 },
405 },
406 'commit_data': {
406 'commit_data': {
407 'commit_id': None
407 'commit_id': None
408 },
408 },
409 'pull_request_data': {'pull_request_id': None},
409 'pull_request_data': {'pull_request_id': None},
410 'timeago': {
410 'timeago': {
411 'refresh_time': 120 * 1000,
411 'refresh_time': 120 * 1000,
412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
413 },
413 },
414 'pyramid_dispatch': {
414 'pyramid_dispatch': {
415
415
416 },
416 },
417 'extra': {'plugins': {}}
417 'extra': {'plugins': {}}
418 }
418 }
419 # END CONFIG VARS
419 # END CONFIG VARS
420
420
421 # TODO: This dosn't work when called from pylons compatibility tween.
421 # TODO: This dosn't work when called from pylons compatibility tween.
422 # Fix this and remove it from base controller.
422 # Fix this and remove it from base controller.
423 # context.repo_name = get_repo_slug(request) # can be empty
423 # context.repo_name = get_repo_slug(request) # can be empty
424
424
425 diffmode = 'sideside'
425 diffmode = 'sideside'
426 if request.GET.get('diffmode'):
426 if request.GET.get('diffmode'):
427 if request.GET['diffmode'] == 'unified':
427 if request.GET['diffmode'] == 'unified':
428 diffmode = 'unified'
428 diffmode = 'unified'
429 elif request.session.get('diffmode'):
429 elif request.session.get('diffmode'):
430 diffmode = request.session['diffmode']
430 diffmode = request.session['diffmode']
431
431
432 context.diffmode = diffmode
432 context.diffmode = diffmode
433
433
434 if request.session.get('diffmode') != diffmode:
434 if request.session.get('diffmode') != diffmode:
435 request.session['diffmode'] = diffmode
435 request.session['diffmode'] = diffmode
436
436
437 context.csrf_token = auth.get_csrf_token(session=request.session)
437 context.csrf_token = auth.get_csrf_token(session=request.session)
438 context.backends = rhodecode.BACKENDS.keys()
438 context.backends = rhodecode.BACKENDS.keys()
439 context.backends.sort()
439 context.backends.sort()
440 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
440 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
441
441
442 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
442 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
443 # given request will ALWAYS be pyramid one
443 # given request will ALWAYS be pyramid one
444 pyramid_request = pyramid.threadlocal.get_current_request()
444 pyramid_request = pyramid.threadlocal.get_current_request()
445 context.pyramid_request = pyramid_request
445 context.pyramid_request = pyramid_request
446
446
447 # web case
447 # web case
448 if hasattr(pyramid_request, 'user'):
448 if hasattr(pyramid_request, 'user'):
449 context.auth_user = pyramid_request.user
449 context.auth_user = pyramid_request.user
450 context.rhodecode_user = pyramid_request.user
450 context.rhodecode_user = pyramid_request.user
451
451
452 # api case
452 # api case
453 if hasattr(pyramid_request, 'rpc_user'):
453 if hasattr(pyramid_request, 'rpc_user'):
454 context.auth_user = pyramid_request.rpc_user
454 context.auth_user = pyramid_request.rpc_user
455 context.rhodecode_user = pyramid_request.rpc_user
455 context.rhodecode_user = pyramid_request.rpc_user
456
456
457 # attach the whole call context to the request
457 # attach the whole call context to the request
458 request.call_context = context
458 request.call_context = context
459
459
460
460
461 def get_auth_user(request):
461 def get_auth_user(request):
462 environ = request.environ
462 environ = request.environ
463 session = request.session
463 session = request.session
464
464
465 ip_addr = get_ip_addr(environ)
465 ip_addr = get_ip_addr(environ)
466 # make sure that we update permissions each time we call controller
466 # make sure that we update permissions each time we call controller
467 _auth_token = (request.GET.get('auth_token', '') or
467 _auth_token = (request.GET.get('auth_token', '') or
468 request.GET.get('api_key', ''))
468 request.GET.get('api_key', ''))
469
469
470 if _auth_token:
470 if _auth_token:
471 # when using API_KEY we assume user exists, and
471 # when using API_KEY we assume user exists, and
472 # doesn't need auth based on cookies.
472 # doesn't need auth based on cookies.
473 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
473 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
474 authenticated = False
474 authenticated = False
475 else:
475 else:
476 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
476 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
477 try:
477 try:
478 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
478 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
479 ip_addr=ip_addr)
479 ip_addr=ip_addr)
480 except UserCreationError as e:
480 except UserCreationError as e:
481 h.flash(e, 'error')
481 h.flash(e, 'error')
482 # container auth or other auth functions that create users
482 # container auth or other auth functions that create users
483 # on the fly can throw this exception signaling that there's
483 # on the fly can throw this exception signaling that there's
484 # issue with user creation, explanation should be provided
484 # issue with user creation, explanation should be provided
485 # in Exception itself. We then create a simple blank
485 # in Exception itself. We then create a simple blank
486 # AuthUser
486 # AuthUser
487 auth_user = AuthUser(ip_addr=ip_addr)
487 auth_user = AuthUser(ip_addr=ip_addr)
488
488
489 if password_changed(auth_user, session):
489 if password_changed(auth_user, session):
490 session.invalidate()
490 session.invalidate()
491 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
491 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
492 auth_user = AuthUser(ip_addr=ip_addr)
492 auth_user = AuthUser(ip_addr=ip_addr)
493
493
494 authenticated = cookie_store.get('is_authenticated')
494 authenticated = cookie_store.get('is_authenticated')
495
495
496 if not auth_user.is_authenticated and auth_user.is_user_object:
496 if not auth_user.is_authenticated and auth_user.is_user_object:
497 # user is not authenticated and not empty
497 # user is not authenticated and not empty
498 auth_user.set_authenticated(authenticated)
498 auth_user.set_authenticated(authenticated)
499
499
500 return auth_user
500 return auth_user
501
501
502
502
503 class BaseController(WSGIController):
503 class BaseController(WSGIController):
504
504
505 def __before__(self):
505 def __before__(self):
506 """
506 """
507 __before__ is called before controller methods and after __call__
507 __before__ is called before controller methods and after __call__
508 """
508 """
509 # on each call propagate settings calls into global settings.
509 # on each call propagate settings calls into global settings.
510 from pylons import config
510 from pylons import config
511 from pylons import tmpl_context as c, request, url
511 from pylons import tmpl_context as c, request, url
512 set_rhodecode_config(config)
512 set_rhodecode_config(config)
513 attach_context_attributes(c, request, self._rhodecode_user.user_id)
513 attach_context_attributes(c, request, self._rhodecode_user.user_id)
514
514
515 # TODO: Remove this when fixed in attach_context_attributes()
515 # TODO: Remove this when fixed in attach_context_attributes()
516 c.repo_name = get_repo_slug(request) # can be empty
516 c.repo_name = get_repo_slug(request) # can be empty
517
517
518 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
518 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
519 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
519 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
520 self.sa = meta.Session
520 self.sa = meta.Session
521 self.scm_model = ScmModel(self.sa)
521 self.scm_model = ScmModel(self.sa)
522
522
523 # set user language
523 # set user language
524 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
524 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
525 if user_lang:
525 if user_lang:
526 translation.set_lang(user_lang)
526 translation.set_lang(user_lang)
527 log.debug('set language to %s for user %s',
527 log.debug('set language to %s for user %s',
528 user_lang, self._rhodecode_user)
528 user_lang, self._rhodecode_user)
529
529
530 def _dispatch_redirect(self, with_url, environ, start_response):
530 def _dispatch_redirect(self, with_url, environ, start_response):
531 from webob.exc import HTTPFound
531 from webob.exc import HTTPFound
532 resp = HTTPFound(with_url)
532 resp = HTTPFound(with_url)
533 environ['SCRIPT_NAME'] = '' # handle prefix middleware
533 environ['SCRIPT_NAME'] = '' # handle prefix middleware
534 environ['PATH_INFO'] = with_url
534 environ['PATH_INFO'] = with_url
535 return resp(environ, start_response)
535 return resp(environ, start_response)
536
536
537 def __call__(self, environ, start_response):
537 def __call__(self, environ, start_response):
538 """Invoke the Controller"""
538 """Invoke the Controller"""
539 # WSGIController.__call__ dispatches to the Controller method
539 # WSGIController.__call__ dispatches to the Controller method
540 # the request is routed to. This routing information is
540 # the request is routed to. This routing information is
541 # available in environ['pylons.routes_dict']
541 # available in environ['pylons.routes_dict']
542 from rhodecode.lib import helpers as h
542 from rhodecode.lib import helpers as h
543 from pylons import tmpl_context as c, request, url
543 from pylons import tmpl_context as c, request, url
544
544
545 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
545 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
546 if environ.get('debugtoolbar.wants_pylons_context', False):
546 if environ.get('debugtoolbar.wants_pylons_context', False):
547 environ['debugtoolbar.pylons_context'] = c._current_obj()
547 environ['debugtoolbar.pylons_context'] = c._current_obj()
548
548
549 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
549 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
550 environ['pylons.routes_dict']['action']])
550 environ['pylons.routes_dict']['action']])
551
551
552 self.rc_config = SettingsModel().get_all_settings(cache=True)
552 self.rc_config = SettingsModel().get_all_settings(cache=True)
553 self.ip_addr = get_ip_addr(environ)
553 self.ip_addr = get_ip_addr(environ)
554
554
555 # The rhodecode auth user is looked up and passed through the
555 # The rhodecode auth user is looked up and passed through the
556 # environ by the pylons compatibility tween in pyramid.
556 # environ by the pylons compatibility tween in pyramid.
557 # So we can just grab it from there.
557 # So we can just grab it from there.
558 auth_user = environ['rc_auth_user']
558 auth_user = environ['rc_auth_user']
559
559
560 # set globals for auth user
560 # set globals for auth user
561 request.user = auth_user
561 request.user = auth_user
562 self._rhodecode_user = auth_user
562 self._rhodecode_user = auth_user
563
563
564 log.info('IP: %s User: %s accessed %s [%s]' % (
564 log.info('IP: %s User: %s accessed %s [%s]' % (
565 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
565 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
566 _route_name)
566 _route_name)
567 )
567 )
568
568
569 user_obj = auth_user.get_instance()
569 user_obj = auth_user.get_instance()
570 if user_obj and user_obj.user_data.get('force_password_change'):
570 if user_obj and user_obj.user_data.get('force_password_change'):
571 h.flash('You are required to change your password', 'warning',
571 h.flash('You are required to change your password', 'warning',
572 ignore_duplicate=True)
572 ignore_duplicate=True)
573 return self._dispatch_redirect(
573 return self._dispatch_redirect(
574 url('my_account_password'), environ, start_response)
574 url('my_account_password'), environ, start_response)
575
575
576 return WSGIController.__call__(self, environ, start_response)
576 return WSGIController.__call__(self, environ, start_response)
577
577
578
578
579 def h_filter(s):
579 def h_filter(s):
580 """
580 """
581 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
581 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
582 we wrap this with additional functionality that converts None to empty
582 we wrap this with additional functionality that converts None to empty
583 strings
583 strings
584 """
584 """
585 if s is None:
585 if s is None:
586 return markupsafe.Markup()
586 return markupsafe.Markup()
587 return markupsafe.escape(s)
587 return markupsafe.escape(s)
588
588
589
589
590 def add_events_routes(config):
590 def add_events_routes(config):
591 """
591 """
592 Adds routing that can be used in events. Because some events are triggered
592 Adds routing that can be used in events. Because some events are triggered
593 outside of pyramid context, we need to bootstrap request with some
593 outside of pyramid context, we need to bootstrap request with some
594 routing registered
594 routing registered
595 """
595 """
596
596
597 from rhodecode.apps._base import ADMIN_PREFIX
597 from rhodecode.apps._base import ADMIN_PREFIX
598
598
599 config.add_route(name='home', pattern='/')
599 config.add_route(name='home', pattern='/')
600
600
601 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
601 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
602 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
602 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
603 config.add_route(name='repo_summary', pattern='/{repo_name}')
603 config.add_route(name='repo_summary', pattern='/{repo_name}')
604 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
604 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
605 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
605 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
606
606
607 config.add_route(name='pullrequest_show',
607 config.add_route(name='pullrequest_show',
608 pattern='/{repo_name}/pull-request/{pull_request_id}')
608 pattern='/{repo_name}/pull-request/{pull_request_id}')
609 config.add_route(name='pull_requests_global',
609 config.add_route(name='pull_requests_global',
610 pattern='/pull-request/{pull_request_id}')
610 pattern='/pull-request/{pull_request_id}')
611 config.add_route(name='repo_commit',
611 config.add_route(name='repo_commit',
612 pattern='/{repo_name}/changeset/{commit_id}')
612 pattern='/{repo_name}/changeset/{commit_id}')
613
613
614 config.add_route(name='repo_files',
614 config.add_route(name='repo_files',
615 pattern='/{repo_name}/files/{commit_id}/{f_path}')
615 pattern='/{repo_name}/files/{commit_id}/{f_path}')
616
616
617
617
618 def bootstrap_config(request):
618 def bootstrap_config(request):
619 import pyramid.testing
619 import pyramid.testing
620 registry = pyramid.testing.Registry('RcTestRegistry')
620 registry = pyramid.testing.Registry('RcTestRegistry')
621
621 config = pyramid.testing.setUp(registry=registry, request=request)
622 config = pyramid.testing.setUp(registry=registry, request=request)
622
623
623 # allow pyramid lookup in testing
624 # allow pyramid lookup in testing
624 config.include('pyramid_mako')
625 config.include('pyramid_mako')
625
626
626 add_events_routes(config)
627 add_events_routes(config)
628
627 return config
629 return config
628
630
629
631
630 def bootstrap_request(**kwargs):
632 def bootstrap_request(**kwargs):
631 import pyramid.testing
633 import pyramid.testing
632
634
633 class TestRequest(pyramid.testing.DummyRequest):
635 class TestRequest(pyramid.testing.DummyRequest):
634 application_url = kwargs.pop('application_url', 'http://example.com')
636 application_url = kwargs.pop('application_url', 'http://example.com')
635 host = kwargs.pop('host', 'example.com:80')
637 host = kwargs.pop('host', 'example.com:80')
636 domain = kwargs.pop('domain', 'example.com')
638 domain = kwargs.pop('domain', 'example.com')
637
639
638 def translate(self, msg):
640 def translate(self, msg):
639 return msg
641 return msg
640
642
643 def plularize(self, singular, plural, n):
644 return singular
645
646 def get_partial_renderer(self, tmpl_name):
647
648 from rhodecode.lib.partial_renderer import get_partial_renderer
649 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
650
651 _call_context = {}
652 @property
653 def call_context(self):
654 return self._call_context
655
641 class TestDummySession(pyramid.testing.DummySession):
656 class TestDummySession(pyramid.testing.DummySession):
642 def save(*arg, **kw):
657 def save(*arg, **kw):
643 pass
658 pass
644
659
645 request = TestRequest(**kwargs)
660 request = TestRequest(**kwargs)
646 request.session = TestDummySession()
661 request.session = TestDummySession()
647
662
648 return request
663 return request
649
664
General Comments 0
You need to be logged in to leave comments. Login now