##// END OF EJS Templates
core: removed usage of global pylons config in base lib.
marcink -
r2000:9e69eb38 default
parent child Browse files
Show More
@@ -1,141 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.model.db import Repository
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.model.repo import RepoModel
28 28 from rhodecode.model.repo_group import RepoGroupModel
29 29 from rhodecode.model.settings import SettingsModel
30 30 from rhodecode.tests import TestController
31 31 from rhodecode.tests.fixture import Fixture
32 32 from rhodecode.lib import helpers as h
33 33
34 34 fixture = Fixture()
35 35
36 36
37 37 def route_path(name, **kwargs):
38 38 return {
39 39 'home': '/',
40 40 'repo_group_home': '/{repo_group_name}'
41 41 }[name].format(**kwargs)
42 42
43 43
44 44 class TestHomeController(TestController):
45 45
46 46 def test_index(self):
47 47 self.log_user()
48 48 response = self.app.get(route_path('home'))
49 49 # if global permission is set
50 50 response.mustcontain('Add Repository')
51 51
52 52 # search for objects inside the JavaScript JSON
53 53 for repo in Repository.getAll():
54 54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
55 55
56 56 def test_index_contains_statics_with_ver(self):
57 57 from rhodecode.lib.base import calculate_version_hash
58 58
59 59 self.log_user()
60 60 response = self.app.get(route_path('home'))
61 61
62 rhodecode_version_hash = calculate_version_hash()
62 rhodecode_version_hash = calculate_version_hash(
63 {'beaker.session.secret':'test-rc-uytcxaz'})
63 64 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
64 65 response.mustcontain('rhodecode-components.js?ver={0}'.format(
65 66 rhodecode_version_hash))
66 67
67 68 def test_index_contains_backend_specific_details(self, backend):
68 69 self.log_user()
69 70 response = self.app.get(route_path('home'))
70 71 tip = backend.repo.get_commit().raw_id
71 72
72 73 # html in javascript variable:
73 74 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
74 75 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
75 76
76 77 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
77 78 response.mustcontain("""Added a symlink""")
78 79
79 80 def test_index_with_anonymous_access_disabled(self):
80 81 with fixture.anon_access(False):
81 82 response = self.app.get(route_path('home'), status=302)
82 83 assert 'login' in response.location
83 84
84 85 def test_index_page_on_groups(self, autologin_user, repo_group):
85 86 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1'))
86 87 response.mustcontain("gr1/repo_in_group")
87 88
88 89 def test_index_page_on_group_with_trailing_slash(
89 90 self, autologin_user, repo_group):
90 91 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1') + '/')
91 92 response.mustcontain("gr1/repo_in_group")
92 93
93 94 @pytest.fixture(scope='class')
94 95 def repo_group(self, request):
95 96 gr = fixture.create_repo_group('gr1')
96 97 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
97 98
98 99 @request.addfinalizer
99 100 def cleanup():
100 101 RepoModel().delete('gr1/repo_in_group')
101 102 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
102 103 Session().commit()
103 104
104 105 def test_index_with_name_with_tags(self, user_util, autologin_user):
105 106 user = user_util.create_user()
106 107 username = user.username
107 108 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
108 109 user.lastname = '#"><img src=x onerror=prompt(document.cookie);>'
109 110
110 111 Session().add(user)
111 112 Session().commit()
112 113 user_util.create_repo(owner=username)
113 114
114 115 response = self.app.get(route_path('home'))
115 116 response.mustcontain(h.html_escape(user.first_name))
116 117 response.mustcontain(h.html_escape(user.last_name))
117 118
118 119 @pytest.mark.parametrize("name, state", [
119 120 ('Disabled', False),
120 121 ('Enabled', True),
121 122 ])
122 123 def test_index_show_version(self, autologin_user, name, state):
123 124 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
124 125
125 126 sett = SettingsModel().create_or_update_setting(
126 127 'show_version', state, 'bool')
127 128 Session().add(sett)
128 129 Session().commit()
129 130 SettingsModel().invalidate_settings_cache()
130 131
131 132 response = self.app.get(route_path('home'))
132 133 if state is True:
133 134 response.mustcontain(version_string)
134 135 if state is False:
135 136 response.mustcontain(no=[version_string])
136 137
137 138 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
138 139 response = self.app.get(route_path('home'))
139 140 assert_response = response.assert_response()
140 141 element = assert_response.get_element('.logout #csrf_token')
141 142 assert element.value == csrf_token
@@ -1,706 +1,712 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import markupsafe
31 31 import ipaddress
32 32 import pyramid.threadlocal
33 33
34 34 from paste.auth.basic import AuthBasicAuthenticator
35 35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
36 36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
37 from pylons import config, tmpl_context as c, request, url
37 from pylons import tmpl_context as c, request, url
38 38 from pylons.controllers import WSGIController
39 39 from pylons.controllers.util import redirect
40 40 from pylons.i18n import translation
41 41 # marcink: don't remove this import
42 42 from pylons.templating import render_mako, pylons_globals, literal, cached_template
43 43 from pylons.i18n.translation import _
44 44 from webob.exc import HTTPFound
45 45
46 46
47 47 import rhodecode
48 48 from rhodecode.authentication.base import VCS_TYPE
49 49 from rhodecode.lib import auth, utils2
50 50 from rhodecode.lib import helpers as h
51 51 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
52 52 from rhodecode.lib.exceptions import UserCreationError
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, set_rhodecode_config, password_changed,
55 55 get_enabled_hook_classes)
56 56 from rhodecode.lib.utils2 import (
57 57 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
58 58 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
59 59 from rhodecode.model import meta
60 60 from rhodecode.model.db import Repository, User, ChangesetComment
61 61 from rhodecode.model.notification import NotificationModel
62 62 from rhodecode.model.scm import ScmModel
63 63 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
64 64
65 65
66 66 log = logging.getLogger(__name__)
67 67
68 68
69 69 # hack to make the migration to pyramid easier
70 70 def render(template_name, extra_vars=None, cache_key=None,
71 71 cache_type=None, cache_expire=None):
72 72 """Render a template with Mako
73 73
74 74 Accepts the cache options ``cache_key``, ``cache_type``, and
75 75 ``cache_expire``.
76 76
77 77 """
78 78 # Create a render callable for the cache function
79 79 def render_template():
80 80 # Pull in extra vars if needed
81 81 globs = extra_vars or {}
82 82
83 83 # Second, get the globals
84 84 globs.update(pylons_globals())
85 85
86 86 globs['_ungettext'] = globs['ungettext']
87 87 # Grab a template reference
88 88 template = globs['app_globals'].mako_lookup.get_template(template_name)
89 89
90 90 return literal(template.render_unicode(**globs))
91 91
92 92 return cached_template(template_name, render_template, cache_key=cache_key,
93 93 cache_type=cache_type, cache_expire=cache_expire)
94 94
95 95 def _filter_proxy(ip):
96 96 """
97 97 Passed in IP addresses in HEADERS can be in a special format of multiple
98 98 ips. Those comma separated IPs are passed from various proxies in the
99 99 chain of request processing. The left-most being the original client.
100 100 We only care about the first IP which came from the org. client.
101 101
102 102 :param ip: ip string from headers
103 103 """
104 104 if ',' in ip:
105 105 _ips = ip.split(',')
106 106 _first_ip = _ips[0].strip()
107 107 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
108 108 return _first_ip
109 109 return ip
110 110
111 111
112 112 def _filter_port(ip):
113 113 """
114 114 Removes a port from ip, there are 4 main cases to handle here.
115 115 - ipv4 eg. 127.0.0.1
116 116 - ipv6 eg. ::1
117 117 - ipv4+port eg. 127.0.0.1:8080
118 118 - ipv6+port eg. [::1]:8080
119 119
120 120 :param ip:
121 121 """
122 122 def is_ipv6(ip_addr):
123 123 if hasattr(socket, 'inet_pton'):
124 124 try:
125 125 socket.inet_pton(socket.AF_INET6, ip_addr)
126 126 except socket.error:
127 127 return False
128 128 else:
129 129 # fallback to ipaddress
130 130 try:
131 131 ipaddress.IPv6Address(safe_unicode(ip_addr))
132 132 except Exception:
133 133 return False
134 134 return True
135 135
136 136 if ':' not in ip: # must be ipv4 pure ip
137 137 return ip
138 138
139 139 if '[' in ip and ']' in ip: # ipv6 with port
140 140 return ip.split(']')[0][1:].lower()
141 141
142 142 # must be ipv6 or ipv4 with port
143 143 if is_ipv6(ip):
144 144 return ip
145 145 else:
146 146 ip, _port = ip.split(':')[:2] # means ipv4+port
147 147 return ip
148 148
149 149
150 150 def get_ip_addr(environ):
151 151 proxy_key = 'HTTP_X_REAL_IP'
152 152 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
153 153 def_key = 'REMOTE_ADDR'
154 154 _filters = lambda x: _filter_port(_filter_proxy(x))
155 155
156 156 ip = environ.get(proxy_key)
157 157 if ip:
158 158 return _filters(ip)
159 159
160 160 ip = environ.get(proxy_key2)
161 161 if ip:
162 162 return _filters(ip)
163 163
164 164 ip = environ.get(def_key, '0.0.0.0')
165 165 return _filters(ip)
166 166
167 167
168 168 def get_server_ip_addr(environ, log_errors=True):
169 169 hostname = environ.get('SERVER_NAME')
170 170 try:
171 171 return socket.gethostbyname(hostname)
172 172 except Exception as e:
173 173 if log_errors:
174 174 # in some cases this lookup is not possible, and we don't want to
175 175 # make it an exception in logs
176 176 log.exception('Could not retrieve server ip address: %s', e)
177 177 return hostname
178 178
179 179
180 180 def get_server_port(environ):
181 181 return environ.get('SERVER_PORT')
182 182
183 183
184 184 def get_access_path(environ):
185 185 path = environ.get('PATH_INFO')
186 186 org_req = environ.get('pylons.original_request')
187 187 if org_req:
188 188 path = org_req.environ.get('PATH_INFO')
189 189 return path
190 190
191 191
192 192 def get_user_agent(environ):
193 193 return environ.get('HTTP_USER_AGENT')
194 194
195 195
196 196 def vcs_operation_context(
197 197 environ, repo_name, username, action, scm, check_locking=True,
198 198 is_shadow_repo=False):
199 199 """
200 200 Generate the context for a vcs operation, e.g. push or pull.
201 201
202 202 This context is passed over the layers so that hooks triggered by the
203 203 vcs operation know details like the user, the user's IP address etc.
204 204
205 205 :param check_locking: Allows to switch of the computation of the locking
206 206 data. This serves mainly the need of the simplevcs middleware to be
207 207 able to disable this for certain operations.
208 208
209 209 """
210 210 # Tri-state value: False: unlock, None: nothing, True: lock
211 211 make_lock = None
212 212 locked_by = [None, None, None]
213 213 is_anonymous = username == User.DEFAULT_USER
214 214 if not is_anonymous and check_locking:
215 215 log.debug('Checking locking on repository "%s"', repo_name)
216 216 user = User.get_by_username(username)
217 217 repo = Repository.get_by_repo_name(repo_name)
218 218 make_lock, __, locked_by = repo.get_locking_state(
219 219 action, user.user_id)
220 220
221 221 settings_model = VcsSettingsModel(repo=repo_name)
222 222 ui_settings = settings_model.get_ui_settings()
223 223
224 224 extras = {
225 225 'ip': get_ip_addr(environ),
226 226 'username': username,
227 227 'action': action,
228 228 'repository': repo_name,
229 229 'scm': scm,
230 230 'config': rhodecode.CONFIG['__file__'],
231 231 'make_lock': make_lock,
232 232 'locked_by': locked_by,
233 233 'server_url': utils2.get_server_url(environ),
234 234 'user_agent': get_user_agent(environ),
235 235 'hooks': get_enabled_hook_classes(ui_settings),
236 236 'is_shadow_repo': is_shadow_repo,
237 237 }
238 238 return extras
239 239
240 240
241 241 class BasicAuth(AuthBasicAuthenticator):
242 242
243 243 def __init__(self, realm, authfunc, registry, auth_http_code=None,
244 244 initial_call_detection=False, acl_repo_name=None):
245 245 self.realm = realm
246 246 self.initial_call = initial_call_detection
247 247 self.authfunc = authfunc
248 248 self.registry = registry
249 249 self.acl_repo_name = acl_repo_name
250 250 self._rc_auth_http_code = auth_http_code
251 251
252 252 def _get_response_from_code(self, http_code):
253 253 try:
254 254 return get_exception(safe_int(http_code))
255 255 except Exception:
256 256 log.exception('Failed to fetch response for code %s' % http_code)
257 257 return HTTPForbidden
258 258
259 259 def build_authentication(self):
260 260 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
261 261 if self._rc_auth_http_code and not self.initial_call:
262 262 # return alternative HTTP code if alternative http return code
263 263 # is specified in RhodeCode config, but ONLY if it's not the
264 264 # FIRST call
265 265 custom_response_klass = self._get_response_from_code(
266 266 self._rc_auth_http_code)
267 267 return custom_response_klass(headers=head)
268 268 return HTTPUnauthorized(headers=head)
269 269
270 270 def authenticate(self, environ):
271 271 authorization = AUTHORIZATION(environ)
272 272 if not authorization:
273 273 return self.build_authentication()
274 274 (authmeth, auth) = authorization.split(' ', 1)
275 275 if 'basic' != authmeth.lower():
276 276 return self.build_authentication()
277 277 auth = auth.strip().decode('base64')
278 278 _parts = auth.split(':', 1)
279 279 if len(_parts) == 2:
280 280 username, password = _parts
281 281 if self.authfunc(
282 282 username, password, environ, VCS_TYPE,
283 283 registry=self.registry, acl_repo_name=self.acl_repo_name):
284 284 return username
285 285 if username and password:
286 286 # we mark that we actually executed authentication once, at
287 287 # that point we can use the alternative auth code
288 288 self.initial_call = False
289 289
290 290 return self.build_authentication()
291 291
292 292 __call__ = authenticate
293 293
294 294
295 def calculate_version_hash():
295 def calculate_version_hash(config):
296 296 return md5(
297 297 config.get('beaker.session.secret', '') +
298 298 rhodecode.__version__)[:8]
299 299
300 300
301 301 def get_current_lang(request):
302 302 # NOTE(marcink): remove after pyramid move
303 303 try:
304 304 return translation.get_lang()[0]
305 305 except:
306 306 pass
307 307
308 308 return getattr(request, '_LOCALE_', request.locale_name)
309 309
310 310
311 311 def attach_context_attributes(context, request, user_id):
312 312 """
313 313 Attach variables into template context called `c`, please note that
314 314 request could be pylons or pyramid request in here.
315 315 """
316 # NOTE(marcink): remove check after pyramid migration
317 if hasattr(request, 'registry'):
318 config = request.registry.settings
319 else:
320 from pylons import config
316 321
317 322 rc_config = SettingsModel().get_all_settings(cache=True)
318 323
319 324 context.rhodecode_version = rhodecode.__version__
320 325 context.rhodecode_edition = config.get('rhodecode.edition')
321 326 # unique secret + version does not leak the version but keep consistency
322 context.rhodecode_version_hash = calculate_version_hash()
327 context.rhodecode_version_hash = calculate_version_hash(config)
323 328
324 329 # Default language set for the incoming request
325 330 context.language = get_current_lang(request)
326 331
327 332 # Visual options
328 333 context.visual = AttributeDict({})
329 334
330 335 # DB stored Visual Items
331 336 context.visual.show_public_icon = str2bool(
332 337 rc_config.get('rhodecode_show_public_icon'))
333 338 context.visual.show_private_icon = str2bool(
334 339 rc_config.get('rhodecode_show_private_icon'))
335 340 context.visual.stylify_metatags = str2bool(
336 341 rc_config.get('rhodecode_stylify_metatags'))
337 342 context.visual.dashboard_items = safe_int(
338 343 rc_config.get('rhodecode_dashboard_items', 100))
339 344 context.visual.admin_grid_items = safe_int(
340 345 rc_config.get('rhodecode_admin_grid_items', 100))
341 346 context.visual.repository_fields = str2bool(
342 347 rc_config.get('rhodecode_repository_fields'))
343 348 context.visual.show_version = str2bool(
344 349 rc_config.get('rhodecode_show_version'))
345 350 context.visual.use_gravatar = str2bool(
346 351 rc_config.get('rhodecode_use_gravatar'))
347 352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
348 353 context.visual.default_renderer = rc_config.get(
349 354 'rhodecode_markup_renderer', 'rst')
350 355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
351 356 context.visual.rhodecode_support_url = \
352 357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
353 358
354 359 context.visual.affected_files_cut_off = 60
355 360
356 361 context.pre_code = rc_config.get('rhodecode_pre_code')
357 362 context.post_code = rc_config.get('rhodecode_post_code')
358 363 context.rhodecode_name = rc_config.get('rhodecode_title')
359 364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
360 365 # if we have specified default_encoding in the request, it has more
361 366 # priority
362 367 if request.GET.get('default_encoding'):
363 368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
364 369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
365 370
366 371 # INI stored
367 372 context.labs_active = str2bool(
368 373 config.get('labs_settings_active', 'false'))
369 374 context.visual.allow_repo_location_change = str2bool(
370 375 config.get('allow_repo_location_change', True))
371 376 context.visual.allow_custom_hooks_settings = str2bool(
372 377 config.get('allow_custom_hooks_settings', True))
373 378 context.debug_style = str2bool(config.get('debug_style', False))
374 379
375 380 context.rhodecode_instanceid = config.get('instance_id')
376 381
377 382 context.visual.cut_off_limit_diff = safe_int(
378 383 config.get('cut_off_limit_diff'))
379 384 context.visual.cut_off_limit_file = safe_int(
380 385 config.get('cut_off_limit_file'))
381 386
382 387 # AppEnlight
383 388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
384 389 context.appenlight_api_public_key = config.get(
385 390 'appenlight.api_public_key', '')
386 391 context.appenlight_server_url = config.get('appenlight.server_url', '')
387 392
388 393 # JS template context
389 394 context.template_context = {
390 395 'repo_name': None,
391 396 'repo_type': None,
392 397 'repo_landing_commit': None,
393 398 'rhodecode_user': {
394 399 'username': None,
395 400 'email': None,
396 401 'notification_status': False
397 402 },
398 403 'visual': {
399 404 'default_renderer': None
400 405 },
401 406 'commit_data': {
402 407 'commit_id': None
403 408 },
404 409 'pull_request_data': {'pull_request_id': None},
405 410 'timeago': {
406 411 'refresh_time': 120 * 1000,
407 412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
408 413 },
409 414 'pylons_dispatch': {
410 415 # 'controller': request.environ['pylons.routes_dict']['controller'],
411 416 # 'action': request.environ['pylons.routes_dict']['action'],
412 417 },
413 418 'pyramid_dispatch': {
414 419
415 420 },
416 421 'extra': {'plugins': {}}
417 422 }
418 423 # END CONFIG VARS
419 424
420 425 # TODO: This dosn't work when called from pylons compatibility tween.
421 426 # Fix this and remove it from base controller.
422 427 # context.repo_name = get_repo_slug(request) # can be empty
423 428
424 429 diffmode = 'sideside'
425 430 if request.GET.get('diffmode'):
426 431 if request.GET['diffmode'] == 'unified':
427 432 diffmode = 'unified'
428 433 elif request.session.get('diffmode'):
429 434 diffmode = request.session['diffmode']
430 435
431 436 context.diffmode = diffmode
432 437
433 438 if request.session.get('diffmode') != diffmode:
434 439 request.session['diffmode'] = diffmode
435 440
436 441 context.csrf_token = auth.get_csrf_token(session=request.session)
437 442 context.backends = rhodecode.BACKENDS.keys()
438 443 context.backends.sort()
439 444 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
440 445
441 446 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
442 447 # given request will ALWAYS be pyramid one
443 448 pyramid_request = pyramid.threadlocal.get_current_request()
444 449 context.pyramid_request = pyramid_request
445 450
446 451 # web case
447 452 if hasattr(pyramid_request, 'user'):
448 453 context.auth_user = pyramid_request.user
449 454 context.rhodecode_user = pyramid_request.user
450 455
451 456 # api case
452 457 if hasattr(pyramid_request, 'rpc_user'):
453 458 context.auth_user = pyramid_request.rpc_user
454 459 context.rhodecode_user = pyramid_request.rpc_user
455 460
456 461 # attach the whole call context to the request
457 462 request.call_context = context
458 463
459 464
460 465 def get_auth_user(request):
461 466 environ = request.environ
462 467 session = request.session
463 468
464 469 ip_addr = get_ip_addr(environ)
465 470 # make sure that we update permissions each time we call controller
466 471 _auth_token = (request.GET.get('auth_token', '') or
467 472 request.GET.get('api_key', ''))
468 473
469 474 if _auth_token:
470 475 # when using API_KEY we assume user exists, and
471 476 # doesn't need auth based on cookies.
472 477 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
473 478 authenticated = False
474 479 else:
475 480 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
476 481 try:
477 482 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
478 483 ip_addr=ip_addr)
479 484 except UserCreationError as e:
480 485 h.flash(e, 'error')
481 486 # container auth or other auth functions that create users
482 487 # on the fly can throw this exception signaling that there's
483 488 # issue with user creation, explanation should be provided
484 489 # in Exception itself. We then create a simple blank
485 490 # AuthUser
486 491 auth_user = AuthUser(ip_addr=ip_addr)
487 492
488 493 if password_changed(auth_user, session):
489 494 session.invalidate()
490 495 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
491 496 auth_user = AuthUser(ip_addr=ip_addr)
492 497
493 498 authenticated = cookie_store.get('is_authenticated')
494 499
495 500 if not auth_user.is_authenticated and auth_user.is_user_object:
496 501 # user is not authenticated and not empty
497 502 auth_user.set_authenticated(authenticated)
498 503
499 504 return auth_user
500 505
501 506
502 507 class BaseController(WSGIController):
503 508
504 509 def __before__(self):
505 510 """
506 511 __before__ is called before controller methods and after __call__
507 512 """
508 513 # on each call propagate settings calls into global settings.
514 from pylons import config
509 515 set_rhodecode_config(config)
510 516 attach_context_attributes(c, request, self._rhodecode_user.user_id)
511 517
512 518 # TODO: Remove this when fixed in attach_context_attributes()
513 519 c.repo_name = get_repo_slug(request) # can be empty
514 520
515 521 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
516 522 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
517 523 self.sa = meta.Session
518 524 self.scm_model = ScmModel(self.sa)
519 525
520 526 # set user language
521 527 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
522 528 if user_lang:
523 529 translation.set_lang(user_lang)
524 530 log.debug('set language to %s for user %s',
525 531 user_lang, self._rhodecode_user)
526 532
527 533 def _dispatch_redirect(self, with_url, environ, start_response):
528 534 resp = HTTPFound(with_url)
529 535 environ['SCRIPT_NAME'] = '' # handle prefix middleware
530 536 environ['PATH_INFO'] = with_url
531 537 return resp(environ, start_response)
532 538
533 539 def __call__(self, environ, start_response):
534 540 """Invoke the Controller"""
535 541 # WSGIController.__call__ dispatches to the Controller method
536 542 # the request is routed to. This routing information is
537 543 # available in environ['pylons.routes_dict']
538 544 from rhodecode.lib import helpers as h
539 545
540 546 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
541 547 if environ.get('debugtoolbar.wants_pylons_context', False):
542 548 environ['debugtoolbar.pylons_context'] = c._current_obj()
543 549
544 550 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
545 551 environ['pylons.routes_dict']['action']])
546 552
547 553 self.rc_config = SettingsModel().get_all_settings(cache=True)
548 554 self.ip_addr = get_ip_addr(environ)
549 555
550 556 # The rhodecode auth user is looked up and passed through the
551 557 # environ by the pylons compatibility tween in pyramid.
552 558 # So we can just grab it from there.
553 559 auth_user = environ['rc_auth_user']
554 560
555 561 # set globals for auth user
556 562 request.user = auth_user
557 563 self._rhodecode_user = auth_user
558 564
559 565 log.info('IP: %s User: %s accessed %s [%s]' % (
560 566 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
561 567 _route_name)
562 568 )
563 569
564 570 user_obj = auth_user.get_instance()
565 571 if user_obj and user_obj.user_data.get('force_password_change'):
566 572 h.flash('You are required to change your password', 'warning',
567 573 ignore_duplicate=True)
568 574 return self._dispatch_redirect(
569 575 url('my_account_password'), environ, start_response)
570 576
571 577 return WSGIController.__call__(self, environ, start_response)
572 578
573 579
574 580 def h_filter(s):
575 581 """
576 582 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
577 583 we wrap this with additional functionality that converts None to empty
578 584 strings
579 585 """
580 586 if s is None:
581 587 return markupsafe.Markup()
582 588 return markupsafe.escape(s)
583 589
584 590
585 591 def add_events_routes(config):
586 592 """
587 593 Adds routing that can be used in events. Because some events are triggered
588 594 outside of pyramid context, we need to bootstrap request with some
589 595 routing registered
590 596 """
591 597 config.add_route(name='home', pattern='/')
592 598
593 599 config.add_route(name='repo_summary', pattern='/{repo_name}')
594 600 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
595 601 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
596 602
597 603 config.add_route(name='pullrequest_show',
598 604 pattern='/{repo_name}/pull-request/{pull_request_id}')
599 605 config.add_route(name='pull_requests_global',
600 606 pattern='/pull-request/{pull_request_id}')
601 607
602 608 config.add_route(name='repo_commit',
603 609 pattern='/{repo_name}/changeset/{commit_id}')
604 610 config.add_route(name='repo_files',
605 611 pattern='/{repo_name}/files/{commit_id}/{f_path}')
606 612
607 613
608 614 def bootstrap_request(**kwargs):
609 615 import pyramid.testing
610 616 request = pyramid.testing.DummyRequest(**kwargs)
611 617 request.application_url = kwargs.pop('application_url', 'http://example.com')
612 618 request.host = kwargs.pop('host', 'example.com:80')
613 619 request.domain = kwargs.pop('domain', 'example.com')
614 620
615 621 config = pyramid.testing.setUp(request=request)
616 622 add_events_routes(config)
617 623
618 624
619 625 class BaseRepoController(BaseController):
620 626 """
621 627 Base class for controllers responsible for loading all needed data for
622 628 repository loaded items are
623 629
624 630 c.rhodecode_repo: instance of scm repository
625 631 c.rhodecode_db_repo: instance of db
626 632 c.repository_requirements_missing: shows that repository specific data
627 633 could not be displayed due to the missing requirements
628 634 c.repository_pull_requests: show number of open pull requests
629 635 """
630 636
631 637 def __before__(self):
632 638 super(BaseRepoController, self).__before__()
633 639 if c.repo_name: # extracted from routes
634 640 db_repo = Repository.get_by_repo_name(c.repo_name)
635 641 if not db_repo:
636 642 return
637 643
638 644 log.debug(
639 645 'Found repository in database %s with state `%s`',
640 646 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
641 647 route = getattr(request.environ.get('routes.route'), 'name', '')
642 648
643 649 # allow to delete repos that are somehow damages in filesystem
644 650 if route in ['delete_repo']:
645 651 return
646 652
647 653 if db_repo.repo_state in [Repository.STATE_PENDING]:
648 654 if route in ['repo_creating_home']:
649 655 return
650 656 check_url = url('repo_creating_home', repo_name=c.repo_name)
651 657 return redirect(check_url)
652 658
653 659 self.rhodecode_db_repo = db_repo
654 660
655 661 missing_requirements = False
656 662 try:
657 663 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
658 664 except RepositoryRequirementError as e:
659 665 missing_requirements = True
660 666 self._handle_missing_requirements(e)
661 667
662 668 if self.rhodecode_repo is None and not missing_requirements:
663 669 log.error('%s this repository is present in database but it '
664 670 'cannot be created as an scm instance', c.repo_name)
665 671
666 672 h.flash(_(
667 673 "The repository at %(repo_name)s cannot be located.") %
668 674 {'repo_name': c.repo_name},
669 675 category='error', ignore_duplicate=True)
670 676 redirect(h.route_path('home'))
671 677
672 678 # update last change according to VCS data
673 679 if not missing_requirements:
674 680 commit = db_repo.get_commit(
675 681 pre_load=["author", "date", "message", "parents"])
676 682 db_repo.update_commit_cache(commit)
677 683
678 684 # Prepare context
679 685 c.rhodecode_db_repo = db_repo
680 686 c.rhodecode_repo = self.rhodecode_repo
681 687 c.repository_requirements_missing = missing_requirements
682 688
683 689 self._update_global_counters(self.scm_model, db_repo)
684 690
685 691 def _update_global_counters(self, scm_model, db_repo):
686 692 """
687 693 Base variables that are exposed to every page of repository
688 694 """
689 695 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
690 696
691 697 def _handle_missing_requirements(self, error):
692 698 self.rhodecode_repo = None
693 699 log.error(
694 700 'Requirements are missing for repository %s: %s',
695 701 c.repo_name, error.message)
696 702
697 703 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
698 704 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
699 705 settings_update_url = url('repo', repo_name=c.repo_name)
700 706 path = request.path
701 707 should_redirect = (
702 708 path not in (summary_url, settings_update_url)
703 709 and '/settings' not in path or path == statistics_url
704 710 )
705 711 if should_redirect:
706 712 redirect(summary_url)
@@ -1,156 +1,154 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The application's model objects
23 23
24 24 :example:
25 25
26 26 .. code-block:: python
27 27
28 28 from paste.deploy import appconfig
29 29 from pylons import config
30 30 from sqlalchemy import engine_from_config
31 31 from rhodecode.config.environment import load_environment
32 32
33 33 conf = appconfig('config:development.ini', relative_to = './../../')
34 34 load_environment(conf.global_conf, conf.local_conf)
35 35
36 36 engine = engine_from_config(config, 'sqlalchemy.')
37 37 init_model(engine)
38 38 # RUN YOUR CODE HERE
39 39
40 40 """
41 41
42 42
43 43 import logging
44 44
45 from pylons import config
46 from pyramid.threadlocal import get_current_registry
47
48 45 from rhodecode.model import meta, db
49 46 from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key
50 47
51 48 log = logging.getLogger(__name__)
52 49
53 50
54 51 def init_model(engine, encryption_key=None):
55 52 """
56 53 Initializes db session, bind the engine with the metadata,
57 54 Call this before using any of the tables or classes in the model,
58 55 preferably once in application start
59 56
60 57 :param engine: engine to bind to
61 58 """
62 59 engine_str = obfuscate_url_pw(str(engine.url))
63 60 log.info("initializing db for %s", engine_str)
64 61 meta.Base.metadata.bind = engine
65 62 db.ENCRYPTION_KEY = encryption_key
66 63
67 64
68 65 def init_model_encryption(migration_models):
66 from pylons import config
69 67 migration_models.ENCRYPTION_KEY = get_encryption_key(config)
70 68 db.ENCRYPTION_KEY = get_encryption_key(config)
71 69
72 70
73 71 class BaseModel(object):
74 72 """
75 73 Base Model for all RhodeCode models, it adds sql alchemy session
76 74 into instance of model
77 75
78 76 :param sa: If passed it reuses this session instead of creating a new one
79 77 """
80 78
81 79 cls = None # override in child class
82 80
83 81 def __init__(self, sa=None):
84 82 if sa is not None:
85 83 self.sa = sa
86 84 else:
87 85 self.sa = meta.Session()
88 86
89 87 def _get_instance(self, cls, instance, callback=None):
90 88 """
91 89 Gets instance of given cls using some simple lookup mechanism.
92 90
93 91 :param cls: classes to fetch
94 92 :param instance: int or Instance
95 93 :param callback: callback to call if all lookups failed
96 94 """
97 95
98 96 if isinstance(instance, cls):
99 97 return instance
100 98 elif isinstance(instance, (int, long)):
101 99 if isinstance(cls, tuple):
102 100 # if we pass multi instances we pick first to .get()
103 101 cls = cls[0]
104 102 return cls.get(instance)
105 103 else:
106 104 if instance:
107 105 if callback is None:
108 106 raise Exception(
109 107 'given object must be int, long or Instance of %s '
110 108 'got %s, no callback provided' % (cls, type(instance))
111 109 )
112 110 else:
113 111 return callback(instance)
114 112
115 113 def _get_user(self, user):
116 114 """
117 115 Helper method to get user by ID, or username fallback
118 116
119 117 :param user: UserID, username, or User instance
120 118 """
121 119 return self._get_instance(
122 120 db.User, user, callback=db.User.get_by_username)
123 121
124 122 def _get_user_group(self, user_group):
125 123 """
126 124 Helper method to get user by ID, or username fallback
127 125
128 126 :param user_group: UserGroupID, user_group_name, or UserGroup instance
129 127 """
130 128 return self._get_instance(
131 129 db.UserGroup, user_group, callback=db.UserGroup.get_by_group_name)
132 130
133 131 def _get_repo(self, repository):
134 132 """
135 133 Helper method to get repository by ID, or repository name
136 134
137 135 :param repository: RepoID, repository name or Repository Instance
138 136 """
139 137 return self._get_instance(
140 138 db.Repository, repository, callback=db.Repository.get_by_repo_name)
141 139
142 140 def _get_perm(self, permission):
143 141 """
144 142 Helper method to get permission by ID, or permission name
145 143
146 144 :param permission: PermissionID, permission_name or Permission instance
147 145 """
148 146 return self._get_instance(
149 147 db.Permission, permission, callback=db.Permission.get_by_key)
150 148
151 149 @classmethod
152 150 def get_all(cls):
153 151 """
154 152 Returns all instances of what is defined in `cls` class variable
155 153 """
156 154 return cls.cls.getAll()
General Comments 0
You need to be logged in to leave comments. Login now