##// END OF EJS Templates
settings: re-use attached request global config for performance...
bart -
r4200:007322e6 stable
parent child Browse files
Show More
@@ -1,615 +1,615 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36
37 37 import rhodecode
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.authentication.base import VCS_TYPE
40 40 from rhodecode.lib import auth, utils2
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 43 from rhodecode.lib.exceptions import UserCreationError
44 44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 45 from rhodecode.lib.utils2 import (
46 46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 48 from rhodecode.model.notification import NotificationModel
49 49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def _filter_proxy(ip):
55 55 """
56 56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 57 ips. Those comma separated IPs are passed from various proxies in the
58 58 chain of request processing. The left-most being the original client.
59 59 We only care about the first IP which came from the org. client.
60 60
61 61 :param ip: ip string from headers
62 62 """
63 63 if ',' in ip:
64 64 _ips = ip.split(',')
65 65 _first_ip = _ips[0].strip()
66 66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 67 return _first_ip
68 68 return ip
69 69
70 70
71 71 def _filter_port(ip):
72 72 """
73 73 Removes a port from ip, there are 4 main cases to handle here.
74 74 - ipv4 eg. 127.0.0.1
75 75 - ipv6 eg. ::1
76 76 - ipv4+port eg. 127.0.0.1:8080
77 77 - ipv6+port eg. [::1]:8080
78 78
79 79 :param ip:
80 80 """
81 81 def is_ipv6(ip_addr):
82 82 if hasattr(socket, 'inet_pton'):
83 83 try:
84 84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 85 except socket.error:
86 86 return False
87 87 else:
88 88 # fallback to ipaddress
89 89 try:
90 90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 91 except Exception:
92 92 return False
93 93 return True
94 94
95 95 if ':' not in ip: # must be ipv4 pure ip
96 96 return ip
97 97
98 98 if '[' in ip and ']' in ip: # ipv6 with port
99 99 return ip.split(']')[0][1:].lower()
100 100
101 101 # must be ipv6 or ipv4 with port
102 102 if is_ipv6(ip):
103 103 return ip
104 104 else:
105 105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 106 return ip
107 107
108 108
109 109 def get_ip_addr(environ):
110 110 proxy_key = 'HTTP_X_REAL_IP'
111 111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 112 def_key = 'REMOTE_ADDR'
113 113 _filters = lambda x: _filter_port(_filter_proxy(x))
114 114
115 115 ip = environ.get(proxy_key)
116 116 if ip:
117 117 return _filters(ip)
118 118
119 119 ip = environ.get(proxy_key2)
120 120 if ip:
121 121 return _filters(ip)
122 122
123 123 ip = environ.get(def_key, '0.0.0.0')
124 124 return _filters(ip)
125 125
126 126
127 127 def get_server_ip_addr(environ, log_errors=True):
128 128 hostname = environ.get('SERVER_NAME')
129 129 try:
130 130 return socket.gethostbyname(hostname)
131 131 except Exception as e:
132 132 if log_errors:
133 133 # in some cases this lookup is not possible, and we don't want to
134 134 # make it an exception in logs
135 135 log.exception('Could not retrieve server ip address: %s', e)
136 136 return hostname
137 137
138 138
139 139 def get_server_port(environ):
140 140 return environ.get('SERVER_PORT')
141 141
142 142
143 143 def get_access_path(environ):
144 144 path = environ.get('PATH_INFO')
145 145 org_req = environ.get('pylons.original_request')
146 146 if org_req:
147 147 path = org_req.environ.get('PATH_INFO')
148 148 return path
149 149
150 150
151 151 def get_user_agent(environ):
152 152 return environ.get('HTTP_USER_AGENT')
153 153
154 154
155 155 def vcs_operation_context(
156 156 environ, repo_name, username, action, scm, check_locking=True,
157 157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 158 """
159 159 Generate the context for a vcs operation, e.g. push or pull.
160 160
161 161 This context is passed over the layers so that hooks triggered by the
162 162 vcs operation know details like the user, the user's IP address etc.
163 163
164 164 :param check_locking: Allows to switch of the computation of the locking
165 165 data. This serves mainly the need of the simplevcs middleware to be
166 166 able to disable this for certain operations.
167 167
168 168 """
169 169 # Tri-state value: False: unlock, None: nothing, True: lock
170 170 make_lock = None
171 171 locked_by = [None, None, None]
172 172 is_anonymous = username == User.DEFAULT_USER
173 173 user = User.get_by_username(username)
174 174 if not is_anonymous and check_locking:
175 175 log.debug('Checking locking on repository "%s"', repo_name)
176 176 repo = Repository.get_by_repo_name(repo_name)
177 177 make_lock, __, locked_by = repo.get_locking_state(
178 178 action, user.user_id)
179 179 user_id = user.user_id
180 180 settings_model = VcsSettingsModel(repo=repo_name)
181 181 ui_settings = settings_model.get_ui_settings()
182 182
183 183 # NOTE(marcink): This should be also in sync with
184 184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 185 store = [x for x in ui_settings if x.key == '/']
186 186 repo_store = ''
187 187 if store:
188 188 repo_store = store[0].value
189 189
190 190 scm_data = {
191 191 'ip': get_ip_addr(environ),
192 192 'username': username,
193 193 'user_id': user_id,
194 194 'action': action,
195 195 'repository': repo_name,
196 196 'scm': scm,
197 197 'config': rhodecode.CONFIG['__file__'],
198 198 'repo_store': repo_store,
199 199 'make_lock': make_lock,
200 200 'locked_by': locked_by,
201 201 'server_url': utils2.get_server_url(environ),
202 202 'user_agent': get_user_agent(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 'detect_force_push': detect_force_push,
206 206 'check_branch_perms': check_branch_perms,
207 207 }
208 208 return scm_data
209 209
210 210
211 211 class BasicAuth(AuthBasicAuthenticator):
212 212
213 213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 214 initial_call_detection=False, acl_repo_name=None):
215 215 self.realm = realm
216 216 self.initial_call = initial_call_detection
217 217 self.authfunc = authfunc
218 218 self.registry = registry
219 219 self.acl_repo_name = acl_repo_name
220 220 self._rc_auth_http_code = auth_http_code
221 221
222 222 def _get_response_from_code(self, http_code):
223 223 try:
224 224 return get_exception(safe_int(http_code))
225 225 except Exception:
226 226 log.exception('Failed to fetch response for code %s', http_code)
227 227 return HTTPForbidden
228 228
229 229 def get_rc_realm(self):
230 230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
231 231
232 232 def build_authentication(self):
233 233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 234 if self._rc_auth_http_code and not self.initial_call:
235 235 # return alternative HTTP code if alternative http return code
236 236 # is specified in RhodeCode config, but ONLY if it's not the
237 237 # FIRST call
238 238 custom_response_klass = self._get_response_from_code(
239 239 self._rc_auth_http_code)
240 240 return custom_response_klass(headers=head)
241 241 return HTTPUnauthorized(headers=head)
242 242
243 243 def authenticate(self, environ):
244 244 authorization = AUTHORIZATION(environ)
245 245 if not authorization:
246 246 return self.build_authentication()
247 247 (authmeth, auth) = authorization.split(' ', 1)
248 248 if 'basic' != authmeth.lower():
249 249 return self.build_authentication()
250 250 auth = auth.strip().decode('base64')
251 251 _parts = auth.split(':', 1)
252 252 if len(_parts) == 2:
253 253 username, password = _parts
254 254 auth_data = self.authfunc(
255 255 username, password, environ, VCS_TYPE,
256 256 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 257 if auth_data:
258 258 return {'username': username, 'auth_data': auth_data}
259 259 if username and password:
260 260 # we mark that we actually executed authentication once, at
261 261 # that point we can use the alternative auth code
262 262 self.initial_call = False
263 263
264 264 return self.build_authentication()
265 265
266 266 __call__ = authenticate
267 267
268 268
269 269 def calculate_version_hash(config):
270 270 return sha1(
271 271 config.get('beaker.session.secret', '') +
272 272 rhodecode.__version__)[:8]
273 273
274 274
275 275 def get_current_lang(request):
276 276 # NOTE(marcink): remove after pyramid move
277 277 try:
278 278 return translation.get_lang()[0]
279 279 except:
280 280 pass
281 281
282 282 return getattr(request, '_LOCALE_', request.locale_name)
283 283
284 284
285 285 def attach_context_attributes(context, request, user_id=None):
286 286 """
287 287 Attach variables into template context called `c`.
288 288 """
289 289 config = request.registry.settings
290 290
291 rc_config = SettingsModel().get_all_settings(cache=True)
291 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
292 292 context.rc_config = rc_config
293 293 context.rhodecode_version = rhodecode.__version__
294 294 context.rhodecode_edition = config.get('rhodecode.edition')
295 295 # unique secret + version does not leak the version but keep consistency
296 296 context.rhodecode_version_hash = calculate_version_hash(config)
297 297
298 298 # Default language set for the incoming request
299 299 context.language = get_current_lang(request)
300 300
301 301 # Visual options
302 302 context.visual = AttributeDict({})
303 303
304 304 # DB stored Visual Items
305 305 context.visual.show_public_icon = str2bool(
306 306 rc_config.get('rhodecode_show_public_icon'))
307 307 context.visual.show_private_icon = str2bool(
308 308 rc_config.get('rhodecode_show_private_icon'))
309 309 context.visual.stylify_metatags = str2bool(
310 310 rc_config.get('rhodecode_stylify_metatags'))
311 311 context.visual.dashboard_items = safe_int(
312 312 rc_config.get('rhodecode_dashboard_items', 100))
313 313 context.visual.admin_grid_items = safe_int(
314 314 rc_config.get('rhodecode_admin_grid_items', 100))
315 315 context.visual.show_revision_number = str2bool(
316 316 rc_config.get('rhodecode_show_revision_number', True))
317 317 context.visual.show_sha_length = safe_int(
318 318 rc_config.get('rhodecode_show_sha_length', 100))
319 319 context.visual.repository_fields = str2bool(
320 320 rc_config.get('rhodecode_repository_fields'))
321 321 context.visual.show_version = str2bool(
322 322 rc_config.get('rhodecode_show_version'))
323 323 context.visual.use_gravatar = str2bool(
324 324 rc_config.get('rhodecode_use_gravatar'))
325 325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
326 326 context.visual.default_renderer = rc_config.get(
327 327 'rhodecode_markup_renderer', 'rst')
328 328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
329 329 context.visual.rhodecode_support_url = \
330 330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
331 331
332 332 context.visual.affected_files_cut_off = 60
333 333
334 334 context.pre_code = rc_config.get('rhodecode_pre_code')
335 335 context.post_code = rc_config.get('rhodecode_post_code')
336 336 context.rhodecode_name = rc_config.get('rhodecode_title')
337 337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
338 338 # if we have specified default_encoding in the request, it has more
339 339 # priority
340 340 if request.GET.get('default_encoding'):
341 341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
342 342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
343 343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
344 344
345 345 # INI stored
346 346 context.labs_active = str2bool(
347 347 config.get('labs_settings_active', 'false'))
348 348 context.ssh_enabled = str2bool(
349 349 config.get('ssh.generate_authorized_keyfile', 'false'))
350 350 context.ssh_key_generator_enabled = str2bool(
351 351 config.get('ssh.enable_ui_key_generator', 'true'))
352 352
353 353 context.visual.allow_repo_location_change = str2bool(
354 354 config.get('allow_repo_location_change', True))
355 355 context.visual.allow_custom_hooks_settings = str2bool(
356 356 config.get('allow_custom_hooks_settings', True))
357 357 context.debug_style = str2bool(config.get('debug_style', False))
358 358
359 359 context.rhodecode_instanceid = config.get('instance_id')
360 360
361 361 context.visual.cut_off_limit_diff = safe_int(
362 362 config.get('cut_off_limit_diff'))
363 363 context.visual.cut_off_limit_file = safe_int(
364 364 config.get('cut_off_limit_file'))
365 365
366 366 context.license = AttributeDict({})
367 367 context.license.hide_license_info = str2bool(
368 368 config.get('license.hide_license_info', False))
369 369
370 370 # AppEnlight
371 371 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
372 372 context.appenlight_api_public_key = config.get(
373 373 'appenlight.api_public_key', '')
374 374 context.appenlight_server_url = config.get('appenlight.server_url', '')
375 375
376 376 diffmode = {
377 377 "unified": "unified",
378 378 "sideside": "sideside"
379 379 }.get(request.GET.get('diffmode'))
380 380
381 381 is_api = hasattr(request, 'rpc_user')
382 382 session_attrs = {
383 383 # defaults
384 384 "clone_url_format": "http",
385 385 "diffmode": "sideside"
386 386 }
387 387
388 388 if not is_api:
389 389 # don't access pyramid session for API calls
390 390 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
391 391 request.session['rc_user_session_attr.diffmode'] = diffmode
392 392
393 393 # session settings per user
394 394
395 395 for k, v in request.session.items():
396 396 pref = 'rc_user_session_attr.'
397 397 if k and k.startswith(pref):
398 398 k = k[len(pref):]
399 399 session_attrs[k] = v
400 400
401 401 context.user_session_attrs = session_attrs
402 402
403 403 # JS template context
404 404 context.template_context = {
405 405 'repo_name': None,
406 406 'repo_type': None,
407 407 'repo_landing_commit': None,
408 408 'rhodecode_user': {
409 409 'username': None,
410 410 'email': None,
411 411 'notification_status': False
412 412 },
413 413 'session_attrs': session_attrs,
414 414 'visual': {
415 415 'default_renderer': None
416 416 },
417 417 'commit_data': {
418 418 'commit_id': None
419 419 },
420 420 'pull_request_data': {'pull_request_id': None},
421 421 'timeago': {
422 422 'refresh_time': 120 * 1000,
423 423 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
424 424 },
425 425 'pyramid_dispatch': {
426 426
427 427 },
428 428 'extra': {'plugins': {}}
429 429 }
430 430 # END CONFIG VARS
431 431 if is_api:
432 432 csrf_token = None
433 433 else:
434 434 csrf_token = auth.get_csrf_token(session=request.session)
435 435
436 436 context.csrf_token = csrf_token
437 437 context.backends = rhodecode.BACKENDS.keys()
438 438 context.backends.sort()
439 439 unread_count = 0
440 440 user_bookmark_list = []
441 441 if user_id:
442 442 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
443 443 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
444 444 context.unread_notifications = unread_count
445 445 context.bookmark_items = user_bookmark_list
446 446
447 447 # web case
448 448 if hasattr(request, 'user'):
449 449 context.auth_user = request.user
450 450 context.rhodecode_user = request.user
451 451
452 452 # api case
453 453 if hasattr(request, 'rpc_user'):
454 454 context.auth_user = request.rpc_user
455 455 context.rhodecode_user = request.rpc_user
456 456
457 457 # attach the whole call context to the request
458 458 request.call_context = context
459 459
460 460
461 461 def get_auth_user(request):
462 462 environ = request.environ
463 463 session = request.session
464 464
465 465 ip_addr = get_ip_addr(environ)
466 466
467 467 # make sure that we update permissions each time we call controller
468 468 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
469 469 if not _auth_token and request.matchdict:
470 470 url_auth_token = request.matchdict.get('_auth_token')
471 471 _auth_token = url_auth_token
472 472 if _auth_token:
473 473 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
474 474
475 475 if _auth_token:
476 476 # when using API_KEY we assume user exists, and
477 477 # doesn't need auth based on cookies.
478 478 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
479 479 authenticated = False
480 480 else:
481 481 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
482 482 try:
483 483 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
484 484 ip_addr=ip_addr)
485 485 except UserCreationError as e:
486 486 h.flash(e, 'error')
487 487 # container auth or other auth functions that create users
488 488 # on the fly can throw this exception signaling that there's
489 489 # issue with user creation, explanation should be provided
490 490 # in Exception itself. We then create a simple blank
491 491 # AuthUser
492 492 auth_user = AuthUser(ip_addr=ip_addr)
493 493
494 494 # in case someone changes a password for user it triggers session
495 495 # flush and forces a re-login
496 496 if password_changed(auth_user, session):
497 497 session.invalidate()
498 498 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
499 499 auth_user = AuthUser(ip_addr=ip_addr)
500 500
501 501 authenticated = cookie_store.get('is_authenticated')
502 502
503 503 if not auth_user.is_authenticated and auth_user.is_user_object:
504 504 # user is not authenticated and not empty
505 505 auth_user.set_authenticated(authenticated)
506 506
507 507 return auth_user, _auth_token
508 508
509 509
510 510 def h_filter(s):
511 511 """
512 512 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
513 513 we wrap this with additional functionality that converts None to empty
514 514 strings
515 515 """
516 516 if s is None:
517 517 return markupsafe.Markup()
518 518 return markupsafe.escape(s)
519 519
520 520
521 521 def add_events_routes(config):
522 522 """
523 523 Adds routing that can be used in events. Because some events are triggered
524 524 outside of pyramid context, we need to bootstrap request with some
525 525 routing registered
526 526 """
527 527
528 528 from rhodecode.apps._base import ADMIN_PREFIX
529 529
530 530 config.add_route(name='home', pattern='/')
531 531 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
532 532 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
533 533
534 534 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
535 535 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
536 536 config.add_route(name='repo_summary', pattern='/{repo_name}')
537 537 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
538 538 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
539 539
540 540 config.add_route(name='pullrequest_show',
541 541 pattern='/{repo_name}/pull-request/{pull_request_id}')
542 542 config.add_route(name='pull_requests_global',
543 543 pattern='/pull-request/{pull_request_id}')
544 544
545 545 config.add_route(name='repo_commit',
546 546 pattern='/{repo_name}/changeset/{commit_id}')
547 547 config.add_route(name='repo_files',
548 548 pattern='/{repo_name}/files/{commit_id}/{f_path}')
549 549
550 550 config.add_route(name='hovercard_user',
551 551 pattern='/_hovercard/user/{user_id}')
552 552
553 553 config.add_route(name='hovercard_user_group',
554 554 pattern='/_hovercard/user_group/{user_group_id}')
555 555
556 556 config.add_route(name='hovercard_pull_request',
557 557 pattern='/_hovercard/pull_request/{pull_request_id}')
558 558
559 559 config.add_route(name='hovercard_repo_commit',
560 560 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
561 561
562 562
563 563 def bootstrap_config(request):
564 564 import pyramid.testing
565 565 registry = pyramid.testing.Registry('RcTestRegistry')
566 566
567 567 config = pyramid.testing.setUp(registry=registry, request=request)
568 568
569 569 # allow pyramid lookup in testing
570 570 config.include('pyramid_mako')
571 571 config.include('rhodecode.lib.rc_beaker')
572 572 config.include('rhodecode.lib.rc_cache')
573 573
574 574 add_events_routes(config)
575 575
576 576 return config
577 577
578 578
579 579 def bootstrap_request(**kwargs):
580 580 import pyramid.testing
581 581
582 582 class TestRequest(pyramid.testing.DummyRequest):
583 583 application_url = kwargs.pop('application_url', 'http://example.com')
584 584 host = kwargs.pop('host', 'example.com:80')
585 585 domain = kwargs.pop('domain', 'example.com')
586 586
587 587 def translate(self, msg):
588 588 return msg
589 589
590 590 def plularize(self, singular, plural, n):
591 591 return singular
592 592
593 593 def get_partial_renderer(self, tmpl_name):
594 594
595 595 from rhodecode.lib.partial_renderer import get_partial_renderer
596 596 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
597 597
598 598 _call_context = TemplateArgs()
599 599 _call_context.visual = TemplateArgs()
600 600 _call_context.visual.show_sha_length = 12
601 601 _call_context.visual.show_revision_number = True
602 602
603 603 @property
604 604 def call_context(self):
605 605 return self._call_context
606 606
607 607 class TestDummySession(pyramid.testing.DummySession):
608 608 def save(*arg, **kw):
609 609 pass
610 610
611 611 request = TestRequest(**kwargs)
612 612 request.session = TestDummySession()
613 613
614 614 return request
615 615
@@ -1,894 +1,906 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 import os
22 22 import hashlib
23 23 import logging
24 24 from collections import namedtuple
25 25 from functools import wraps
26 26 import bleach
27 from pyramid.threadlocal import get_current_request
27 28
28 29 from rhodecode.lib import rc_cache
29 30 from rhodecode.lib.utils2 import (
30 31 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
31 32 from rhodecode.lib.vcs.backends import base
32 33 from rhodecode.model import BaseModel
33 34 from rhodecode.model.db import (
34 35 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
35 36 from rhodecode.model.meta import Session
36 37
37 38
38 39 log = logging.getLogger(__name__)
39 40
40 41
41 42 UiSetting = namedtuple(
42 43 'UiSetting', ['section', 'key', 'value', 'active'])
43 44
44 45 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
45 46
46 47
47 48 class SettingNotFound(Exception):
48 49 def __init__(self, setting_id):
49 50 msg = 'Setting `{}` is not found'.format(setting_id)
50 51 super(SettingNotFound, self).__init__(msg)
51 52
52 53
53 54 class SettingsModel(BaseModel):
54 55 BUILTIN_HOOKS = (
55 56 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
56 57 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
57 58 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
58 59 RhodeCodeUi.HOOK_PUSH_KEY,)
59 60 HOOKS_SECTION = 'hooks'
60 61
61 62 def __init__(self, sa=None, repo=None):
62 63 self.repo = repo
63 64 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
64 65 self.SettingsDbModel = (
65 66 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
66 67 super(SettingsModel, self).__init__(sa)
67 68
68 69 def get_ui_by_key(self, key):
69 70 q = self.UiDbModel.query()
70 71 q = q.filter(self.UiDbModel.ui_key == key)
71 72 q = self._filter_by_repo(RepoRhodeCodeUi, q)
72 73 return q.scalar()
73 74
74 75 def get_ui_by_section(self, section):
75 76 q = self.UiDbModel.query()
76 77 q = q.filter(self.UiDbModel.ui_section == section)
77 78 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 79 return q.all()
79 80
80 81 def get_ui_by_section_and_key(self, section, key):
81 82 q = self.UiDbModel.query()
82 83 q = q.filter(self.UiDbModel.ui_section == section)
83 84 q = q.filter(self.UiDbModel.ui_key == key)
84 85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 86 return q.scalar()
86 87
87 88 def get_ui(self, section=None, key=None):
88 89 q = self.UiDbModel.query()
89 90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90 91
91 92 if section:
92 93 q = q.filter(self.UiDbModel.ui_section == section)
93 94 if key:
94 95 q = q.filter(self.UiDbModel.ui_key == key)
95 96
96 97 # TODO: mikhail: add caching
97 98 result = [
98 99 UiSetting(
99 100 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
100 101 value=safe_str(r.ui_value), active=r.ui_active
101 102 )
102 103 for r in q.all()
103 104 ]
104 105 return result
105 106
106 107 def get_builtin_hooks(self):
107 108 q = self.UiDbModel.query()
108 109 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 110 return self._get_hooks(q)
110 111
111 112 def get_custom_hooks(self):
112 113 q = self.UiDbModel.query()
113 114 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 115 return self._get_hooks(q)
115 116
116 117 def create_ui_section_value(self, section, val, key=None, active=True):
117 118 new_ui = self.UiDbModel()
118 119 new_ui.ui_section = section
119 120 new_ui.ui_value = val
120 121 new_ui.ui_active = active
121 122
122 123 repository_id = ''
123 124 if self.repo:
124 125 repo = self._get_repo(self.repo)
125 126 repository_id = repo.repo_id
126 127 new_ui.repository_id = repository_id
127 128
128 129 if not key:
129 130 # keys are unique so they need appended info
130 131 if self.repo:
131 132 key = hashlib.sha1(
132 133 '{}{}{}'.format(section, val, repository_id)).hexdigest()
133 134 else:
134 135 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
135 136
136 137 new_ui.ui_key = key
137 138
138 139 Session().add(new_ui)
139 140 return new_ui
140 141
141 142 def create_or_update_hook(self, key, value):
142 143 ui = (
143 144 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
144 145 self.UiDbModel())
145 146 ui.ui_section = self.HOOKS_SECTION
146 147 ui.ui_active = True
147 148 ui.ui_key = key
148 149 ui.ui_value = value
149 150
150 151 if self.repo:
151 152 repo = self._get_repo(self.repo)
152 153 repository_id = repo.repo_id
153 154 ui.repository_id = repository_id
154 155
155 156 Session().add(ui)
156 157 return ui
157 158
158 159 def delete_ui(self, id_):
159 160 ui = self.UiDbModel.get(id_)
160 161 if not ui:
161 162 raise SettingNotFound(id_)
162 163 Session().delete(ui)
163 164
164 165 def get_setting_by_name(self, name):
165 166 q = self._get_settings_query()
166 167 q = q.filter(self.SettingsDbModel.app_settings_name == name)
167 168 return q.scalar()
168 169
169 170 def create_or_update_setting(
170 171 self, name, val=Optional(''), type_=Optional('unicode')):
171 172 """
172 173 Creates or updates RhodeCode setting. If updates is triggered it will
173 174 only update parameters that are explicityl set Optional instance will
174 175 be skipped
175 176
176 177 :param name:
177 178 :param val:
178 179 :param type_:
179 180 :return:
180 181 """
181 182
182 183 res = self.get_setting_by_name(name)
183 184 repo = self._get_repo(self.repo) if self.repo else None
184 185
185 186 if not res:
186 187 val = Optional.extract(val)
187 188 type_ = Optional.extract(type_)
188 189
189 190 args = (
190 191 (repo.repo_id, name, val, type_)
191 192 if repo else (name, val, type_))
192 193 res = self.SettingsDbModel(*args)
193 194
194 195 else:
195 196 if self.repo:
196 197 res.repository_id = repo.repo_id
197 198
198 199 res.app_settings_name = name
199 200 if not isinstance(type_, Optional):
200 201 # update if set
201 202 res.app_settings_type = type_
202 203 if not isinstance(val, Optional):
203 204 # update if set
204 205 res.app_settings_value = val
205 206
206 207 Session().add(res)
207 208 return res
208 209
209 210 def invalidate_settings_cache(self):
210 211 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
211 212 CacheKey.set_invalidate(invalidation_namespace)
212 213
213 def get_all_settings(self, cache=False):
214 def get_all_settings(self, cache=False, from_request=True):
215 # defines if we use GLOBAL, or PER_REPO
216 repo = self._get_repo(self.repo) if self.repo else None
217 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
218
219 # initially try the requests context, this is the fastest
220 # we only fetch global config
221 if from_request:
222 request = get_current_request()
223
224 if request and not repo and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
225 rc_config = request.call_context.rc_config
226 if rc_config:
227 return rc_config
228
214 229 region = rc_cache.get_or_create_region('sql_cache_short')
215 230 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
216 231
217 232 @region.conditional_cache_on_arguments(condition=cache)
218 233 def _get_all_settings(name, key):
219 234 q = self._get_settings_query()
220 235 if not q:
221 236 raise Exception('Could not get application settings !')
222 237
223 238 settings = {
224 239 'rhodecode_' + result.app_settings_name: result.app_settings_value
225 240 for result in q
226 241 }
227 242 return settings
228 243
229 repo = self._get_repo(self.repo) if self.repo else None
230 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
231
232 244 inv_context_manager = rc_cache.InvalidationContext(
233 245 uid='cache_settings', invalidation_namespace=invalidation_namespace)
234 246 with inv_context_manager as invalidation_context:
235 247 # check for stored invalidation signal, and maybe purge the cache
236 248 # before computing it again
237 249 if invalidation_context.should_invalidate():
238 250 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
239 251 # reads different settings etc. It's little too much but those caches
240 252 # are anyway very short lived and it's a safest way.
241 253 region = rc_cache.get_or_create_region('sql_cache_short')
242 254 region.invalidate()
243 255
244 256 result = _get_all_settings('rhodecode_settings', key)
245 257 log.debug('Fetching app settings for key: %s took: %.4fs', key,
246 258 inv_context_manager.compute_time)
247 259
248 260 return result
249 261
250 262 def get_auth_settings(self):
251 263 q = self._get_settings_query()
252 264 q = q.filter(
253 265 self.SettingsDbModel.app_settings_name.startswith('auth_'))
254 266 rows = q.all()
255 267 auth_settings = {
256 268 row.app_settings_name: row.app_settings_value for row in rows}
257 269 return auth_settings
258 270
259 271 def get_auth_plugins(self):
260 272 auth_plugins = self.get_setting_by_name("auth_plugins")
261 273 return auth_plugins.app_settings_value
262 274
263 275 def get_default_repo_settings(self, strip_prefix=False):
264 276 q = self._get_settings_query()
265 277 q = q.filter(
266 278 self.SettingsDbModel.app_settings_name.startswith('default_'))
267 279 rows = q.all()
268 280
269 281 result = {}
270 282 for row in rows:
271 283 key = row.app_settings_name
272 284 if strip_prefix:
273 285 key = remove_prefix(key, prefix='default_')
274 286 result.update({key: row.app_settings_value})
275 287 return result
276 288
277 289 def get_repo(self):
278 290 repo = self._get_repo(self.repo)
279 291 if not repo:
280 292 raise Exception(
281 293 'Repository `{}` cannot be found inside the database'.format(
282 294 self.repo))
283 295 return repo
284 296
285 297 def _filter_by_repo(self, model, query):
286 298 if self.repo:
287 299 repo = self.get_repo()
288 300 query = query.filter(model.repository_id == repo.repo_id)
289 301 return query
290 302
291 303 def _get_hooks(self, query):
292 304 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
293 305 query = self._filter_by_repo(RepoRhodeCodeUi, query)
294 306 return query.all()
295 307
296 308 def _get_settings_query(self):
297 309 q = self.SettingsDbModel.query()
298 310 return self._filter_by_repo(RepoRhodeCodeSetting, q)
299 311
300 312 def list_enabled_social_plugins(self, settings):
301 313 enabled = []
302 314 for plug in SOCIAL_PLUGINS_LIST:
303 315 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
304 316 )):
305 317 enabled.append(plug)
306 318 return enabled
307 319
308 320
309 321 def assert_repo_settings(func):
310 322 @wraps(func)
311 323 def _wrapper(self, *args, **kwargs):
312 324 if not self.repo_settings:
313 325 raise Exception('Repository is not specified')
314 326 return func(self, *args, **kwargs)
315 327 return _wrapper
316 328
317 329
318 330 class IssueTrackerSettingsModel(object):
319 331 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
320 332 SETTINGS_PREFIX = 'issuetracker_'
321 333
322 334 def __init__(self, sa=None, repo=None):
323 335 self.global_settings = SettingsModel(sa=sa)
324 336 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
325 337
326 338 @property
327 339 def inherit_global_settings(self):
328 340 if not self.repo_settings:
329 341 return True
330 342 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
331 343 return setting.app_settings_value if setting else True
332 344
333 345 @inherit_global_settings.setter
334 346 def inherit_global_settings(self, value):
335 347 if self.repo_settings:
336 348 settings = self.repo_settings.create_or_update_setting(
337 349 self.INHERIT_SETTINGS, value, type_='bool')
338 350 Session().add(settings)
339 351
340 352 def _get_keyname(self, key, uid, prefix=''):
341 353 return '{0}{1}{2}_{3}'.format(
342 354 prefix, self.SETTINGS_PREFIX, key, uid)
343 355
344 356 def _make_dict_for_settings(self, qs):
345 357 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
346 358
347 359 issuetracker_entries = {}
348 360 # create keys
349 361 for k, v in qs.items():
350 362 if k.startswith(prefix_match):
351 363 uid = k[len(prefix_match):]
352 364 issuetracker_entries[uid] = None
353 365
354 366 def url_cleaner(input_str):
355 367 input_str = input_str.replace('"', '').replace("'", '')
356 368 input_str = bleach.clean(input_str, strip=True)
357 369 return input_str
358 370
359 371 # populate
360 372 for uid in issuetracker_entries:
361 373 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
362 374
363 375 issuetracker_entries[uid] = AttributeDict({
364 376 'pat': qs.get(
365 377 self._get_keyname('pat', uid, 'rhodecode_')),
366 378 'url': url_cleaner(
367 379 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
368 380 'pref': bleach.clean(
369 381 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
370 382 'desc': qs.get(
371 383 self._get_keyname('desc', uid, 'rhodecode_')),
372 384 })
373 385
374 386 return issuetracker_entries
375 387
376 388 def get_global_settings(self, cache=False):
377 389 """
378 390 Returns list of global issue tracker settings
379 391 """
380 392 defaults = self.global_settings.get_all_settings(cache=cache)
381 393 settings = self._make_dict_for_settings(defaults)
382 394 return settings
383 395
384 396 def get_repo_settings(self, cache=False):
385 397 """
386 398 Returns list of issue tracker settings per repository
387 399 """
388 400 if not self.repo_settings:
389 401 raise Exception('Repository is not specified')
390 402 all_settings = self.repo_settings.get_all_settings(cache=cache)
391 403 settings = self._make_dict_for_settings(all_settings)
392 404 return settings
393 405
394 406 def get_settings(self, cache=False):
395 407 if self.inherit_global_settings:
396 408 return self.get_global_settings(cache=cache)
397 409 else:
398 410 return self.get_repo_settings(cache=cache)
399 411
400 412 def delete_entries(self, uid):
401 413 if self.repo_settings:
402 414 all_patterns = self.get_repo_settings()
403 415 settings_model = self.repo_settings
404 416 else:
405 417 all_patterns = self.get_global_settings()
406 418 settings_model = self.global_settings
407 419 entries = all_patterns.get(uid, [])
408 420
409 421 for del_key in entries:
410 422 setting_name = self._get_keyname(del_key, uid)
411 423 entry = settings_model.get_setting_by_name(setting_name)
412 424 if entry:
413 425 Session().delete(entry)
414 426
415 427 Session().commit()
416 428
417 429 def create_or_update_setting(
418 430 self, name, val=Optional(''), type_=Optional('unicode')):
419 431 if self.repo_settings:
420 432 setting = self.repo_settings.create_or_update_setting(
421 433 name, val, type_)
422 434 else:
423 435 setting = self.global_settings.create_or_update_setting(
424 436 name, val, type_)
425 437 return setting
426 438
427 439
428 440 class VcsSettingsModel(object):
429 441
430 442 INHERIT_SETTINGS = 'inherit_vcs_settings'
431 443 GENERAL_SETTINGS = (
432 444 'use_outdated_comments',
433 445 'pr_merge_enabled',
434 446 'hg_use_rebase_for_merging',
435 447 'hg_close_branch_before_merging',
436 448 'git_use_rebase_for_merging',
437 449 'git_close_branch_before_merging',
438 450 'diff_cache',
439 451 )
440 452
441 453 HOOKS_SETTINGS = (
442 454 ('hooks', 'changegroup.repo_size'),
443 455 ('hooks', 'changegroup.push_logger'),
444 456 ('hooks', 'outgoing.pull_logger'),
445 457 )
446 458 HG_SETTINGS = (
447 459 ('extensions', 'largefiles'),
448 460 ('phases', 'publish'),
449 461 ('extensions', 'evolve'),
450 462 ('extensions', 'topic'),
451 463 ('experimental', 'evolution'),
452 464 ('experimental', 'evolution.exchange'),
453 465 )
454 466 GIT_SETTINGS = (
455 467 ('vcs_git_lfs', 'enabled'),
456 468 )
457 469 GLOBAL_HG_SETTINGS = (
458 470 ('extensions', 'largefiles'),
459 471 ('largefiles', 'usercache'),
460 472 ('phases', 'publish'),
461 473 ('extensions', 'hgsubversion'),
462 474 ('extensions', 'evolve'),
463 475 ('extensions', 'topic'),
464 476 ('experimental', 'evolution'),
465 477 ('experimental', 'evolution.exchange'),
466 478 )
467 479
468 480 GLOBAL_GIT_SETTINGS = (
469 481 ('vcs_git_lfs', 'enabled'),
470 482 ('vcs_git_lfs', 'store_location')
471 483 )
472 484
473 485 GLOBAL_SVN_SETTINGS = (
474 486 ('vcs_svn_proxy', 'http_requests_enabled'),
475 487 ('vcs_svn_proxy', 'http_server_url')
476 488 )
477 489
478 490 SVN_BRANCH_SECTION = 'vcs_svn_branch'
479 491 SVN_TAG_SECTION = 'vcs_svn_tag'
480 492 SSL_SETTING = ('web', 'push_ssl')
481 493 PATH_SETTING = ('paths', '/')
482 494
483 495 def __init__(self, sa=None, repo=None):
484 496 self.global_settings = SettingsModel(sa=sa)
485 497 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
486 498 self._ui_settings = (
487 499 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
488 500 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
489 501
490 502 @property
491 503 @assert_repo_settings
492 504 def inherit_global_settings(self):
493 505 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
494 506 return setting.app_settings_value if setting else True
495 507
496 508 @inherit_global_settings.setter
497 509 @assert_repo_settings
498 510 def inherit_global_settings(self, value):
499 511 self.repo_settings.create_or_update_setting(
500 512 self.INHERIT_SETTINGS, value, type_='bool')
501 513
502 514 def get_global_svn_branch_patterns(self):
503 515 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
504 516
505 517 @assert_repo_settings
506 518 def get_repo_svn_branch_patterns(self):
507 519 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
508 520
509 521 def get_global_svn_tag_patterns(self):
510 522 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
511 523
512 524 @assert_repo_settings
513 525 def get_repo_svn_tag_patterns(self):
514 526 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
515 527
516 528 def get_global_settings(self):
517 529 return self._collect_all_settings(global_=True)
518 530
519 531 @assert_repo_settings
520 532 def get_repo_settings(self):
521 533 return self._collect_all_settings(global_=False)
522 534
523 535 @assert_repo_settings
524 536 def get_repo_settings_inherited(self):
525 537 global_settings = self.get_global_settings()
526 538 global_settings.update(self.get_repo_settings())
527 539 return global_settings
528 540
529 541 @assert_repo_settings
530 542 def create_or_update_repo_settings(
531 543 self, data, inherit_global_settings=False):
532 544 from rhodecode.model.scm import ScmModel
533 545
534 546 self.inherit_global_settings = inherit_global_settings
535 547
536 548 repo = self.repo_settings.get_repo()
537 549 if not inherit_global_settings:
538 550 if repo.repo_type == 'svn':
539 551 self.create_repo_svn_settings(data)
540 552 else:
541 553 self.create_or_update_repo_hook_settings(data)
542 554 self.create_or_update_repo_pr_settings(data)
543 555
544 556 if repo.repo_type == 'hg':
545 557 self.create_or_update_repo_hg_settings(data)
546 558
547 559 if repo.repo_type == 'git':
548 560 self.create_or_update_repo_git_settings(data)
549 561
550 562 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
551 563
552 564 @assert_repo_settings
553 565 def create_or_update_repo_hook_settings(self, data):
554 566 for section, key in self.HOOKS_SETTINGS:
555 567 data_key = self._get_form_ui_key(section, key)
556 568 if data_key not in data:
557 569 raise ValueError(
558 570 'The given data does not contain {} key'.format(data_key))
559 571
560 572 active = data.get(data_key)
561 573 repo_setting = self.repo_settings.get_ui_by_section_and_key(
562 574 section, key)
563 575 if not repo_setting:
564 576 global_setting = self.global_settings.\
565 577 get_ui_by_section_and_key(section, key)
566 578 self.repo_settings.create_ui_section_value(
567 579 section, global_setting.ui_value, key=key, active=active)
568 580 else:
569 581 repo_setting.ui_active = active
570 582 Session().add(repo_setting)
571 583
572 584 def update_global_hook_settings(self, data):
573 585 for section, key in self.HOOKS_SETTINGS:
574 586 data_key = self._get_form_ui_key(section, key)
575 587 if data_key not in data:
576 588 raise ValueError(
577 589 'The given data does not contain {} key'.format(data_key))
578 590 active = data.get(data_key)
579 591 repo_setting = self.global_settings.get_ui_by_section_and_key(
580 592 section, key)
581 593 repo_setting.ui_active = active
582 594 Session().add(repo_setting)
583 595
584 596 @assert_repo_settings
585 597 def create_or_update_repo_pr_settings(self, data):
586 598 return self._create_or_update_general_settings(
587 599 self.repo_settings, data)
588 600
589 601 def create_or_update_global_pr_settings(self, data):
590 602 return self._create_or_update_general_settings(
591 603 self.global_settings, data)
592 604
593 605 @assert_repo_settings
594 606 def create_repo_svn_settings(self, data):
595 607 return self._create_svn_settings(self.repo_settings, data)
596 608
597 609 def _set_evolution(self, settings, is_enabled):
598 610 if is_enabled:
599 611 # if evolve is active set evolution=all
600 612
601 613 self._create_or_update_ui(
602 614 settings, *('experimental', 'evolution'), value='all',
603 615 active=True)
604 616 self._create_or_update_ui(
605 617 settings, *('experimental', 'evolution.exchange'), value='yes',
606 618 active=True)
607 619 # if evolve is active set topics server support
608 620 self._create_or_update_ui(
609 621 settings, *('extensions', 'topic'), value='',
610 622 active=True)
611 623
612 624 else:
613 625 self._create_or_update_ui(
614 626 settings, *('experimental', 'evolution'), value='',
615 627 active=False)
616 628 self._create_or_update_ui(
617 629 settings, *('experimental', 'evolution.exchange'), value='no',
618 630 active=False)
619 631 self._create_or_update_ui(
620 632 settings, *('extensions', 'topic'), value='',
621 633 active=False)
622 634
623 635 @assert_repo_settings
624 636 def create_or_update_repo_hg_settings(self, data):
625 637 largefiles, phases, evolve = \
626 638 self.HG_SETTINGS[:3]
627 639 largefiles_key, phases_key, evolve_key = \
628 640 self._get_settings_keys(self.HG_SETTINGS[:3], data)
629 641
630 642 self._create_or_update_ui(
631 643 self.repo_settings, *largefiles, value='',
632 644 active=data[largefiles_key])
633 645 self._create_or_update_ui(
634 646 self.repo_settings, *evolve, value='',
635 647 active=data[evolve_key])
636 648 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
637 649
638 650 self._create_or_update_ui(
639 651 self.repo_settings, *phases, value=safe_str(data[phases_key]))
640 652
641 653 def create_or_update_global_hg_settings(self, data):
642 654 largefiles, largefiles_store, phases, hgsubversion, evolve \
643 655 = self.GLOBAL_HG_SETTINGS[:5]
644 656 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
645 657 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
646 658
647 659 self._create_or_update_ui(
648 660 self.global_settings, *largefiles, value='',
649 661 active=data[largefiles_key])
650 662 self._create_or_update_ui(
651 663 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
652 664 self._create_or_update_ui(
653 665 self.global_settings, *phases, value=safe_str(data[phases_key]))
654 666 self._create_or_update_ui(
655 667 self.global_settings, *hgsubversion, active=data[subversion_key])
656 668 self._create_or_update_ui(
657 669 self.global_settings, *evolve, value='',
658 670 active=data[evolve_key])
659 671 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
660 672
661 673 def create_or_update_repo_git_settings(self, data):
662 674 # NOTE(marcink): # comma makes unpack work properly
663 675 lfs_enabled, \
664 676 = self.GIT_SETTINGS
665 677
666 678 lfs_enabled_key, \
667 679 = self._get_settings_keys(self.GIT_SETTINGS, data)
668 680
669 681 self._create_or_update_ui(
670 682 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
671 683 active=data[lfs_enabled_key])
672 684
673 685 def create_or_update_global_git_settings(self, data):
674 686 lfs_enabled, lfs_store_location \
675 687 = self.GLOBAL_GIT_SETTINGS
676 688 lfs_enabled_key, lfs_store_location_key \
677 689 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
678 690
679 691 self._create_or_update_ui(
680 692 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
681 693 active=data[lfs_enabled_key])
682 694 self._create_or_update_ui(
683 695 self.global_settings, *lfs_store_location,
684 696 value=data[lfs_store_location_key])
685 697
686 698 def create_or_update_global_svn_settings(self, data):
687 699 # branch/tags patterns
688 700 self._create_svn_settings(self.global_settings, data)
689 701
690 702 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
691 703 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
692 704 self.GLOBAL_SVN_SETTINGS, data)
693 705
694 706 self._create_or_update_ui(
695 707 self.global_settings, *http_requests_enabled,
696 708 value=safe_str(data[http_requests_enabled_key]))
697 709 self._create_or_update_ui(
698 710 self.global_settings, *http_server_url,
699 711 value=data[http_server_url_key])
700 712
701 713 def update_global_ssl_setting(self, value):
702 714 self._create_or_update_ui(
703 715 self.global_settings, *self.SSL_SETTING, value=value)
704 716
705 717 def update_global_path_setting(self, value):
706 718 self._create_or_update_ui(
707 719 self.global_settings, *self.PATH_SETTING, value=value)
708 720
709 721 @assert_repo_settings
710 722 def delete_repo_svn_pattern(self, id_):
711 723 ui = self.repo_settings.UiDbModel.get(id_)
712 724 if ui and ui.repository.repo_name == self.repo_settings.repo:
713 725 # only delete if it's the same repo as initialized settings
714 726 self.repo_settings.delete_ui(id_)
715 727 else:
716 728 # raise error as if we wouldn't find this option
717 729 self.repo_settings.delete_ui(-1)
718 730
719 731 def delete_global_svn_pattern(self, id_):
720 732 self.global_settings.delete_ui(id_)
721 733
722 734 @assert_repo_settings
723 735 def get_repo_ui_settings(self, section=None, key=None):
724 736 global_uis = self.global_settings.get_ui(section, key)
725 737 repo_uis = self.repo_settings.get_ui(section, key)
726 738
727 739 filtered_repo_uis = self._filter_ui_settings(repo_uis)
728 740 filtered_repo_uis_keys = [
729 741 (s.section, s.key) for s in filtered_repo_uis]
730 742
731 743 def _is_global_ui_filtered(ui):
732 744 return (
733 745 (ui.section, ui.key) in filtered_repo_uis_keys
734 746 or ui.section in self._svn_sections)
735 747
736 748 filtered_global_uis = [
737 749 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
738 750
739 751 return filtered_global_uis + filtered_repo_uis
740 752
741 753 def get_global_ui_settings(self, section=None, key=None):
742 754 return self.global_settings.get_ui(section, key)
743 755
744 756 def get_ui_settings_as_config_obj(self, section=None, key=None):
745 757 config = base.Config()
746 758
747 759 ui_settings = self.get_ui_settings(section=section, key=key)
748 760
749 761 for entry in ui_settings:
750 762 config.set(entry.section, entry.key, entry.value)
751 763
752 764 return config
753 765
754 766 def get_ui_settings(self, section=None, key=None):
755 767 if not self.repo_settings or self.inherit_global_settings:
756 768 return self.get_global_ui_settings(section, key)
757 769 else:
758 770 return self.get_repo_ui_settings(section, key)
759 771
760 772 def get_svn_patterns(self, section=None):
761 773 if not self.repo_settings:
762 774 return self.get_global_ui_settings(section)
763 775 else:
764 776 return self.get_repo_ui_settings(section)
765 777
766 778 @assert_repo_settings
767 779 def get_repo_general_settings(self):
768 780 global_settings = self.global_settings.get_all_settings()
769 781 repo_settings = self.repo_settings.get_all_settings()
770 782 filtered_repo_settings = self._filter_general_settings(repo_settings)
771 783 global_settings.update(filtered_repo_settings)
772 784 return global_settings
773 785
774 786 def get_global_general_settings(self):
775 787 return self.global_settings.get_all_settings()
776 788
777 789 def get_general_settings(self):
778 790 if not self.repo_settings or self.inherit_global_settings:
779 791 return self.get_global_general_settings()
780 792 else:
781 793 return self.get_repo_general_settings()
782 794
783 795 def get_repos_location(self):
784 796 return self.global_settings.get_ui_by_key('/').ui_value
785 797
786 798 def _filter_ui_settings(self, settings):
787 799 filtered_settings = [
788 800 s for s in settings if self._should_keep_setting(s)]
789 801 return filtered_settings
790 802
791 803 def _should_keep_setting(self, setting):
792 804 keep = (
793 805 (setting.section, setting.key) in self._ui_settings or
794 806 setting.section in self._svn_sections)
795 807 return keep
796 808
797 809 def _filter_general_settings(self, settings):
798 810 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
799 811 return {
800 812 k: settings[k]
801 813 for k in settings if k in keys}
802 814
803 815 def _collect_all_settings(self, global_=False):
804 816 settings = self.global_settings if global_ else self.repo_settings
805 817 result = {}
806 818
807 819 for section, key in self._ui_settings:
808 820 ui = settings.get_ui_by_section_and_key(section, key)
809 821 result_key = self._get_form_ui_key(section, key)
810 822
811 823 if ui:
812 824 if section in ('hooks', 'extensions'):
813 825 result[result_key] = ui.ui_active
814 826 elif result_key in ['vcs_git_lfs_enabled']:
815 827 result[result_key] = ui.ui_active
816 828 else:
817 829 result[result_key] = ui.ui_value
818 830
819 831 for name in self.GENERAL_SETTINGS:
820 832 setting = settings.get_setting_by_name(name)
821 833 if setting:
822 834 result_key = 'rhodecode_{}'.format(name)
823 835 result[result_key] = setting.app_settings_value
824 836
825 837 return result
826 838
827 839 def _get_form_ui_key(self, section, key):
828 840 return '{section}_{key}'.format(
829 841 section=section, key=key.replace('.', '_'))
830 842
831 843 def _create_or_update_ui(
832 844 self, settings, section, key, value=None, active=None):
833 845 ui = settings.get_ui_by_section_and_key(section, key)
834 846 if not ui:
835 847 active = True if active is None else active
836 848 settings.create_ui_section_value(
837 849 section, value, key=key, active=active)
838 850 else:
839 851 if active is not None:
840 852 ui.ui_active = active
841 853 if value is not None:
842 854 ui.ui_value = value
843 855 Session().add(ui)
844 856
845 857 def _create_svn_settings(self, settings, data):
846 858 svn_settings = {
847 859 'new_svn_branch': self.SVN_BRANCH_SECTION,
848 860 'new_svn_tag': self.SVN_TAG_SECTION
849 861 }
850 862 for key in svn_settings:
851 863 if data.get(key):
852 864 settings.create_ui_section_value(svn_settings[key], data[key])
853 865
854 866 def _create_or_update_general_settings(self, settings, data):
855 867 for name in self.GENERAL_SETTINGS:
856 868 data_key = 'rhodecode_{}'.format(name)
857 869 if data_key not in data:
858 870 raise ValueError(
859 871 'The given data does not contain {} key'.format(data_key))
860 872 setting = settings.create_or_update_setting(
861 873 name, data[data_key], 'bool')
862 874 Session().add(setting)
863 875
864 876 def _get_settings_keys(self, settings, data):
865 877 data_keys = [self._get_form_ui_key(*s) for s in settings]
866 878 for data_key in data_keys:
867 879 if data_key not in data:
868 880 raise ValueError(
869 881 'The given data does not contain {} key'.format(data_key))
870 882 return data_keys
871 883
872 884 def create_largeobjects_dirs_if_needed(self, repo_store_path):
873 885 """
874 886 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
875 887 does a repository scan if enabled in the settings.
876 888 """
877 889
878 890 from rhodecode.lib.vcs.backends.hg import largefiles_store
879 891 from rhodecode.lib.vcs.backends.git import lfs_store
880 892
881 893 paths = [
882 894 largefiles_store(repo_store_path),
883 895 lfs_store(repo_store_path)]
884 896
885 897 for path in paths:
886 898 if os.path.isdir(path):
887 899 continue
888 900 if os.path.isfile(path):
889 901 continue
890 902 # not a file nor dir, we try to create it
891 903 try:
892 904 os.makedirs(path)
893 905 except Exception:
894 906 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now