##// END OF EJS Templates
settings: fixed some issuetracker settings tests
super-admin -
r4835:39136da6 default
parent child Browse files
Show More
@@ -1,742 +1,767 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 mock
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.apps._base import ADMIN_PREFIX
26 26 from rhodecode.lib.utils2 import md5
27 27 from rhodecode.model.db import RhodeCodeUi
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 30 from rhodecode.tests import assert_session_flash
31 31 from rhodecode.tests.utils import AssertResponse
32 32
33 33
34 34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
35 35
36 36
37 37 def route_path(name, params=None, **kwargs):
38 38 import urllib
39 39 from rhodecode.apps._base import ADMIN_PREFIX
40 40
41 41 base_url = {
42 42
43 43 'admin_settings':
44 44 ADMIN_PREFIX +'/settings',
45 45 'admin_settings_update':
46 46 ADMIN_PREFIX + '/settings/update',
47 47 'admin_settings_global':
48 48 ADMIN_PREFIX + '/settings/global',
49 49 'admin_settings_global_update':
50 50 ADMIN_PREFIX + '/settings/global/update',
51 51 'admin_settings_vcs':
52 52 ADMIN_PREFIX + '/settings/vcs',
53 53 'admin_settings_vcs_update':
54 54 ADMIN_PREFIX + '/settings/vcs/update',
55 55 'admin_settings_vcs_svn_pattern_delete':
56 56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 57 'admin_settings_mapping':
58 58 ADMIN_PREFIX + '/settings/mapping',
59 59 'admin_settings_mapping_update':
60 60 ADMIN_PREFIX + '/settings/mapping/update',
61 61 'admin_settings_visual':
62 62 ADMIN_PREFIX + '/settings/visual',
63 63 'admin_settings_visual_update':
64 64 ADMIN_PREFIX + '/settings/visual/update',
65 65 'admin_settings_issuetracker':
66 66 ADMIN_PREFIX + '/settings/issue-tracker',
67 67 'admin_settings_issuetracker_update':
68 68 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 69 'admin_settings_issuetracker_test':
70 70 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 71 'admin_settings_issuetracker_delete':
72 72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 73 'admin_settings_email':
74 74 ADMIN_PREFIX + '/settings/email',
75 75 'admin_settings_email_update':
76 76 ADMIN_PREFIX + '/settings/email/update',
77 77 'admin_settings_hooks':
78 78 ADMIN_PREFIX + '/settings/hooks',
79 79 'admin_settings_hooks_update':
80 80 ADMIN_PREFIX + '/settings/hooks/update',
81 81 'admin_settings_hooks_delete':
82 82 ADMIN_PREFIX + '/settings/hooks/delete',
83 83 'admin_settings_search':
84 84 ADMIN_PREFIX + '/settings/search',
85 85 'admin_settings_labs':
86 86 ADMIN_PREFIX + '/settings/labs',
87 87 'admin_settings_labs_update':
88 88 ADMIN_PREFIX + '/settings/labs/update',
89 89
90 90 'admin_settings_sessions':
91 91 ADMIN_PREFIX + '/settings/sessions',
92 92 'admin_settings_sessions_cleanup':
93 93 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 94 'admin_settings_system':
95 95 ADMIN_PREFIX + '/settings/system',
96 96 'admin_settings_system_update':
97 97 ADMIN_PREFIX + '/settings/system/updates',
98 98 'admin_settings_open_source':
99 99 ADMIN_PREFIX + '/settings/open_source',
100 100
101 101
102 102 }[name].format(**kwargs)
103 103
104 104 if params:
105 105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
106 106 return base_url
107 107
108 108
109 109 @pytest.mark.usefixtures('autologin_user', 'app')
110 110 class TestAdminSettingsController(object):
111 111
112 112 @pytest.mark.parametrize('urlname', [
113 113 'admin_settings_vcs',
114 114 'admin_settings_mapping',
115 115 'admin_settings_global',
116 116 'admin_settings_visual',
117 117 'admin_settings_email',
118 118 'admin_settings_hooks',
119 119 'admin_settings_search',
120 120 ])
121 121 def test_simple_get(self, urlname):
122 122 self.app.get(route_path(urlname))
123 123
124 124 def test_create_custom_hook(self, csrf_token):
125 125 response = self.app.post(
126 126 route_path('admin_settings_hooks_update'),
127 127 params={
128 128 'new_hook_ui_key': 'test_hooks_1',
129 129 'new_hook_ui_value': 'cd /tmp',
130 130 'csrf_token': csrf_token})
131 131
132 132 response = response.follow()
133 133 response.mustcontain('test_hooks_1')
134 134 response.mustcontain('cd /tmp')
135 135
136 136 def test_create_custom_hook_delete(self, csrf_token):
137 137 response = self.app.post(
138 138 route_path('admin_settings_hooks_update'),
139 139 params={
140 140 'new_hook_ui_key': 'test_hooks_2',
141 141 'new_hook_ui_value': 'cd /tmp2',
142 142 'csrf_token': csrf_token})
143 143
144 144 response = response.follow()
145 145 response.mustcontain('test_hooks_2')
146 146 response.mustcontain('cd /tmp2')
147 147
148 148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149 149
150 150 # delete
151 151 self.app.post(
152 152 route_path('admin_settings_hooks_delete'),
153 153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 154 response = self.app.get(route_path('admin_settings_hooks'))
155 155 response.mustcontain(no=['test_hooks_2'])
156 156 response.mustcontain(no=['cd /tmp2'])
157 157
158 158
159 159 @pytest.mark.usefixtures('autologin_user', 'app')
160 160 class TestAdminSettingsGlobal(object):
161 161
162 162 def test_pre_post_code_code_active(self, csrf_token):
163 163 pre_code = 'rc-pre-code-187652122'
164 164 post_code = 'rc-postcode-98165231'
165 165
166 166 response = self.post_and_verify_settings({
167 167 'rhodecode_pre_code': pre_code,
168 168 'rhodecode_post_code': post_code,
169 169 'csrf_token': csrf_token,
170 170 })
171 171
172 172 response = response.follow()
173 173 response.mustcontain(pre_code, post_code)
174 174
175 175 def test_pre_post_code_code_inactive(self, csrf_token):
176 176 pre_code = 'rc-pre-code-187652122'
177 177 post_code = 'rc-postcode-98165231'
178 178 response = self.post_and_verify_settings({
179 179 'rhodecode_pre_code': '',
180 180 'rhodecode_post_code': '',
181 181 'csrf_token': csrf_token,
182 182 })
183 183
184 184 response = response.follow()
185 185 response.mustcontain(no=[pre_code, post_code])
186 186
187 187 def test_captcha_activate(self, csrf_token):
188 188 self.post_and_verify_settings({
189 189 'rhodecode_captcha_private_key': '1234567890',
190 190 'rhodecode_captcha_public_key': '1234567890',
191 191 'csrf_token': csrf_token,
192 192 })
193 193
194 194 response = self.app.get(ADMIN_PREFIX + '/register')
195 195 response.mustcontain('captcha')
196 196
197 197 def test_captcha_deactivate(self, csrf_token):
198 198 self.post_and_verify_settings({
199 199 'rhodecode_captcha_private_key': '',
200 200 'rhodecode_captcha_public_key': '1234567890',
201 201 'csrf_token': csrf_token,
202 202 })
203 203
204 204 response = self.app.get(ADMIN_PREFIX + '/register')
205 205 response.mustcontain(no=['captcha'])
206 206
207 207 def test_title_change(self, csrf_token):
208 208 old_title = 'RhodeCode'
209 209
210 210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
211 211 response = self.post_and_verify_settings({
212 212 'rhodecode_title': new_title,
213 213 'csrf_token': csrf_token,
214 214 })
215 215
216 216 response = response.follow()
217 217 response.mustcontain(new_title)
218 218
219 219 def post_and_verify_settings(self, settings):
220 220 old_title = 'RhodeCode'
221 221 old_realm = 'RhodeCode authentication'
222 222 params = {
223 223 'rhodecode_title': old_title,
224 224 'rhodecode_realm': old_realm,
225 225 'rhodecode_pre_code': '',
226 226 'rhodecode_post_code': '',
227 227 'rhodecode_captcha_private_key': '',
228 228 'rhodecode_captcha_public_key': '',
229 229 'rhodecode_create_personal_repo_group': False,
230 230 'rhodecode_personal_repo_group_pattern': '${username}',
231 231 }
232 232 params.update(settings)
233 233 response = self.app.post(
234 234 route_path('admin_settings_global_update'), params=params)
235 235
236 236 assert_session_flash(response, 'Updated application settings')
237 237 app_settings = SettingsModel().get_all_settings()
238 238 del settings['csrf_token']
239 239 for key, value in settings.iteritems():
240 240 assert app_settings[key] == value.decode('utf-8')
241 241
242 242 return response
243 243
244 244
245 245 @pytest.mark.usefixtures('autologin_user', 'app')
246 246 class TestAdminSettingsVcs(object):
247 247
248 248 def test_contains_svn_default_patterns(self):
249 249 response = self.app.get(route_path('admin_settings_vcs'))
250 250 expected_patterns = [
251 251 '/trunk',
252 252 '/branches/*',
253 253 '/tags/*',
254 254 ]
255 255 for pattern in expected_patterns:
256 256 response.mustcontain(pattern)
257 257
258 258 def test_add_new_svn_branch_and_tag_pattern(
259 259 self, backend_svn, form_defaults, disable_sql_cache,
260 260 csrf_token):
261 261 form_defaults.update({
262 262 'new_svn_branch': '/exp/branches/*',
263 263 'new_svn_tag': '/important_tags/*',
264 264 'csrf_token': csrf_token,
265 265 })
266 266
267 267 response = self.app.post(
268 268 route_path('admin_settings_vcs_update'),
269 269 params=form_defaults, status=302)
270 270 response = response.follow()
271 271
272 272 # Expect to find the new values on the page
273 273 response.mustcontain('/exp/branches/*')
274 274 response.mustcontain('/important_tags/*')
275 275
276 276 # Expect that those patterns are used to match branches and tags now
277 277 repo = backend_svn['svn-simple-layout'].scm_instance()
278 278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
279 279 assert 'important_tags/v0.5' in repo.tags
280 280
281 281 def test_add_same_svn_value_twice_shows_an_error_message(
282 282 self, form_defaults, csrf_token, settings_util):
283 283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
284 284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
285 285
286 286 response = self.app.post(
287 287 route_path('admin_settings_vcs_update'),
288 288 params={
289 289 'paths_root_path': form_defaults['paths_root_path'],
290 290 'new_svn_branch': '/test',
291 291 'new_svn_tag': '/test',
292 292 'csrf_token': csrf_token,
293 293 },
294 294 status=200)
295 295
296 296 response.mustcontain("Pattern already exists")
297 297 response.mustcontain("Some form inputs contain invalid data.")
298 298
299 299 @pytest.mark.parametrize('section', [
300 300 'vcs_svn_branch',
301 301 'vcs_svn_tag',
302 302 ])
303 303 def test_delete_svn_patterns(
304 304 self, section, csrf_token, settings_util):
305 305 setting = settings_util.create_rhodecode_ui(
306 306 section, '/test_delete', cleanup=False)
307 307
308 308 self.app.post(
309 309 route_path('admin_settings_vcs_svn_pattern_delete'),
310 310 params={
311 311 'delete_svn_pattern': setting.ui_id,
312 312 'csrf_token': csrf_token},
313 313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
314 314
315 315 @pytest.mark.parametrize('section', [
316 316 'vcs_svn_branch',
317 317 'vcs_svn_tag',
318 318 ])
319 319 def test_delete_svn_patterns_raises_404_when_no_xhr(
320 320 self, section, csrf_token, settings_util):
321 321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
322 322
323 323 self.app.post(
324 324 route_path('admin_settings_vcs_svn_pattern_delete'),
325 325 params={
326 326 'delete_svn_pattern': setting.ui_id,
327 327 'csrf_token': csrf_token},
328 328 status=404)
329 329
330 330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
331 331 form_defaults.update({
332 332 'csrf_token': csrf_token,
333 333 'extensions_hgsubversion': 'True',
334 334 })
335 335 response = self.app.post(
336 336 route_path('admin_settings_vcs_update'),
337 337 params=form_defaults,
338 338 status=302)
339 339
340 340 response = response.follow()
341 341 extensions_input = (
342 342 '<input id="extensions_hgsubversion" '
343 343 'name="extensions_hgsubversion" type="checkbox" '
344 344 'value="True" checked="checked" />')
345 345 response.mustcontain(extensions_input)
346 346
347 347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
348 348 form_defaults.update({
349 349 'csrf_token': csrf_token,
350 350 'extensions_evolve': 'True',
351 351 })
352 352 response = self.app.post(
353 353 route_path('admin_settings_vcs_update'),
354 354 params=form_defaults,
355 355 status=302)
356 356
357 357 response = response.follow()
358 358 extensions_input = (
359 359 '<input id="extensions_evolve" '
360 360 'name="extensions_evolve" type="checkbox" '
361 361 'value="True" checked="checked" />')
362 362 response.mustcontain(extensions_input)
363 363
364 364 def test_has_a_section_for_pull_request_settings(self):
365 365 response = self.app.get(route_path('admin_settings_vcs'))
366 366 response.mustcontain('Pull Request Settings')
367 367
368 368 def test_has_an_input_for_invalidation_of_inline_comments(self):
369 369 response = self.app.get(route_path('admin_settings_vcs'))
370 370 assert_response = response.assert_response()
371 371 assert_response.one_element_exists(
372 372 '[name=rhodecode_use_outdated_comments]')
373 373
374 374 @pytest.mark.parametrize('new_value', [True, False])
375 375 def test_allows_to_change_invalidation_of_inline_comments(
376 376 self, form_defaults, csrf_token, new_value):
377 377 setting_key = 'use_outdated_comments'
378 378 setting = SettingsModel().create_or_update_setting(
379 379 setting_key, not new_value, 'bool')
380 380 Session().add(setting)
381 381 Session().commit()
382 382
383 383 form_defaults.update({
384 384 'csrf_token': csrf_token,
385 385 'rhodecode_use_outdated_comments': str(new_value),
386 386 })
387 387 response = self.app.post(
388 388 route_path('admin_settings_vcs_update'),
389 389 params=form_defaults,
390 390 status=302)
391 391 response = response.follow()
392 392 setting = SettingsModel().get_setting_by_name(setting_key)
393 393 assert setting.app_settings_value is new_value
394 394
395 395 @pytest.mark.parametrize('new_value', [True, False])
396 396 def test_allows_to_change_hg_rebase_merge_strategy(
397 397 self, form_defaults, csrf_token, new_value):
398 398 setting_key = 'hg_use_rebase_for_merging'
399 399
400 400 form_defaults.update({
401 401 'csrf_token': csrf_token,
402 402 'rhodecode_' + setting_key: str(new_value),
403 403 })
404 404
405 405 with mock.patch.dict(
406 406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
407 407 self.app.post(
408 408 route_path('admin_settings_vcs_update'),
409 409 params=form_defaults,
410 410 status=302)
411 411
412 412 setting = SettingsModel().get_setting_by_name(setting_key)
413 413 assert setting.app_settings_value is new_value
414 414
415 415 @pytest.fixture()
416 416 def disable_sql_cache(self, request):
417 417 patcher = mock.patch(
418 418 'rhodecode.lib.caching_query.FromCache.process_query')
419 419 request.addfinalizer(patcher.stop)
420 420 patcher.start()
421 421
422 422 @pytest.fixture()
423 423 def form_defaults(self):
424 424 from rhodecode.apps.admin.views.settings import AdminSettingsView
425 425 return AdminSettingsView._form_defaults()
426 426
427 427 # TODO: johbo: What we really want is to checkpoint before a test run and
428 428 # reset the session afterwards.
429 429 @pytest.fixture(scope='class', autouse=True)
430 430 def cleanup_settings(self, request, baseapp):
431 431 ui_id = RhodeCodeUi.ui_id
432 432 original_ids = list(
433 433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
434 434
435 435 @request.addfinalizer
436 436 def cleanup():
437 437 RhodeCodeUi.query().filter(
438 438 ui_id.notin_(original_ids)).delete(False)
439 439
440 440
441 441 @pytest.mark.usefixtures('autologin_user', 'app')
442 442 class TestLabsSettings(object):
443 443 def test_get_settings_page_disabled(self):
444 444 with mock.patch.dict(
445 445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
446 446
447 447 response = self.app.get(
448 448 route_path('admin_settings_labs'), status=302)
449 449
450 450 assert response.location.endswith(route_path('admin_settings'))
451 451
452 452 def test_get_settings_page_enabled(self):
453 453 from rhodecode.apps.admin.views import settings
454 454 lab_settings = [
455 455 settings.LabSetting(
456 456 key='rhodecode_bool',
457 457 type='bool',
458 458 group='bool group',
459 459 label='bool label',
460 460 help='bool help'
461 461 ),
462 462 settings.LabSetting(
463 463 key='rhodecode_text',
464 464 type='unicode',
465 465 group='text group',
466 466 label='text label',
467 467 help='text help'
468 468 ),
469 469 ]
470 470 with mock.patch.dict(rhodecode.CONFIG,
471 471 {'labs_settings_active': 'true'}):
472 472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
473 473 response = self.app.get(route_path('admin_settings_labs'))
474 474
475 475 assert '<label>bool group:</label>' in response
476 476 assert '<label for="rhodecode_bool">bool label</label>' in response
477 477 assert '<p class="help-block">bool help</p>' in response
478 478 assert 'name="rhodecode_bool" type="checkbox"' in response
479 479
480 480 assert '<label>text group:</label>' in response
481 481 assert '<label for="rhodecode_text">text label</label>' in response
482 482 assert '<p class="help-block">text help</p>' in response
483 483 assert 'name="rhodecode_text" size="60" type="text"' in response
484 484
485 485
486 486 @pytest.mark.usefixtures('app')
487 487 class TestOpenSourceLicenses(object):
488 488
489 489 def test_records_are_displayed(self, autologin_user):
490 490 sample_licenses = [
491 491 {
492 492 "license": [
493 493 {
494 494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
495 495 "shortName": "bsdOriginal",
496 496 "spdxId": "BSD-4-Clause",
497 497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
498 498 }
499 499 ],
500 500 "name": "python2.7-coverage-3.7.1"
501 501 },
502 502 {
503 503 "license": [
504 504 {
505 505 "fullName": "MIT License",
506 506 "shortName": "mit",
507 507 "spdxId": "MIT",
508 508 "url": "http://spdx.org/licenses/MIT.html"
509 509 }
510 510 ],
511 511 "name": "python2.7-bootstrapped-pip-9.0.1"
512 512 },
513 513 ]
514 514 read_licenses_patch = mock.patch(
515 515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
516 516 return_value=sample_licenses)
517 517 with read_licenses_patch:
518 518 response = self.app.get(
519 519 route_path('admin_settings_open_source'), status=200)
520 520
521 521 assert_response = response.assert_response()
522 522 assert_response.element_contains(
523 523 '.panel-heading', 'Licenses of Third Party Packages')
524 524 for license_data in sample_licenses:
525 525 response.mustcontain(license_data["license"][0]["spdxId"])
526 526 assert_response.element_contains('.panel-body', license_data["name"])
527 527
528 528 def test_records_can_be_read(self, autologin_user):
529 529 response = self.app.get(
530 530 route_path('admin_settings_open_source'), status=200)
531 531 assert_response = response.assert_response()
532 532 assert_response.element_contains(
533 533 '.panel-heading', 'Licenses of Third Party Packages')
534 534
535 535 def test_forbidden_when_normal_user(self, autologin_regular_user):
536 536 self.app.get(
537 537 route_path('admin_settings_open_source'), status=404)
538 538
539 539
540 540 @pytest.mark.usefixtures('app')
541 541 class TestUserSessions(object):
542 542
543 543 def test_forbidden_when_normal_user(self, autologin_regular_user):
544 544 self.app.get(route_path('admin_settings_sessions'), status=404)
545 545
546 546 def test_show_sessions_page(self, autologin_user):
547 547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
548 548 response.mustcontain('file')
549 549
550 550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
551 551
552 552 post_data = {
553 553 'csrf_token': csrf_token,
554 554 'expire_days': '60'
555 555 }
556 556 response = self.app.post(
557 557 route_path('admin_settings_sessions_cleanup'), params=post_data,
558 558 status=302)
559 559 assert_session_flash(response, 'Cleaned up old sessions')
560 560
561 561
562 562 @pytest.mark.usefixtures('app')
563 563 class TestAdminSystemInfo(object):
564 564
565 565 def test_forbidden_when_normal_user(self, autologin_regular_user):
566 566 self.app.get(route_path('admin_settings_system'), status=404)
567 567
568 568 def test_system_info_page(self, autologin_user):
569 569 response = self.app.get(route_path('admin_settings_system'))
570 570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
571 571 rhodecode.__version__))
572 572
573 573 def test_system_update_new_version(self, autologin_user):
574 574 update_data = {
575 575 'versions': [
576 576 {
577 577 'version': '100.3.1415926535',
578 578 'general': 'The latest version we are ever going to ship'
579 579 },
580 580 {
581 581 'version': '0.0.0',
582 582 'general': 'The first version we ever shipped'
583 583 }
584 584 ]
585 585 }
586 586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 587 response = self.app.get(route_path('admin_settings_system_update'))
588 588 response.mustcontain('A <b>new version</b> is available')
589 589
590 590 def test_system_update_nothing_new(self, autologin_user):
591 591 update_data = {
592 592 'versions': [
593 593 {
594 594 'version': '0.0.0',
595 595 'general': 'The first version we ever shipped'
596 596 }
597 597 ]
598 598 }
599 599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
600 600 response = self.app.get(route_path('admin_settings_system_update'))
601 601 response.mustcontain(
602 602 'This instance is already running the <b>latest</b> stable version')
603 603
604 604 def test_system_update_bad_response(self, autologin_user):
605 605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
606 606 response = self.app.get(route_path('admin_settings_system_update'))
607 607 response.mustcontain(
608 608 'Bad data sent from update server')
609 609
610 610
611 611 @pytest.mark.usefixtures("app")
612 612 class TestAdminSettingsIssueTracker(object):
613 613 RC_PREFIX = 'rhodecode_'
614 614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
615 615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
616 DESC_KEY = RC_PREFIX + 'issuetracker_desc_'
616 617
617 618 def test_issuetracker_index(self, autologin_user):
618 619 response = self.app.get(route_path('admin_settings_issuetracker'))
619 620 assert response.status_code == 200
620 621
621 622 def test_add_empty_issuetracker_pattern(
622 623 self, request, autologin_user, csrf_token):
623 624 post_url = route_path('admin_settings_issuetracker_update')
624 625 post_data = {
625 626 'csrf_token': csrf_token
626 627 }
627 628 self.app.post(post_url, post_data, status=302)
628 629
629 630 def test_add_issuetracker_pattern(
630 631 self, request, autologin_user, csrf_token):
631 632 pattern = 'issuetracker_pat'
632 633 another_pattern = pattern+'1'
633 634 post_url = route_path('admin_settings_issuetracker_update')
634 635 post_data = {
635 636 'new_pattern_pattern_0': pattern,
636 637 'new_pattern_url_0': 'http://url',
637 638 'new_pattern_prefix_0': 'prefix',
638 639 'new_pattern_description_0': 'description',
639 640 'new_pattern_pattern_1': another_pattern,
640 641 'new_pattern_url_1': 'https://url1',
641 642 'new_pattern_prefix_1': 'prefix1',
642 643 'new_pattern_description_1': 'description1',
643 644 'csrf_token': csrf_token
644 645 }
645 646 self.app.post(post_url, post_data, status=302)
646 647 settings = SettingsModel().get_all_settings()
647 648 self.uid = md5(pattern)
648 649 assert settings[self.PATTERN_KEY+self.uid] == pattern
649 650 self.another_uid = md5(another_pattern)
650 651 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
651 652
652 653 @request.addfinalizer
653 654 def cleanup():
654 655 defaults = SettingsModel().get_all_settings()
655 656
656 657 entries = [name for name in defaults if (
657 658 (self.uid in name) or (self.another_uid) in name)]
658 659 start = len(self.RC_PREFIX)
659 660 for del_key in entries:
660 661 # TODO: anderson: get_by_name needs name without prefix
661 662 entry = SettingsModel().get_setting_by_name(del_key[start:])
662 663 Session().delete(entry)
663 664
664 665 Session().commit()
665 666
666 667 def test_edit_issuetracker_pattern(
667 668 self, autologin_user, backend, csrf_token, request):
668 old_pattern = 'issuetracker_pat'
669
670 old_pattern = 'issuetracker_pat1'
669 671 old_uid = md5(old_pattern)
670 pattern = 'issuetracker_pat_new'
671 self.new_uid = md5(pattern)
672
673 SettingsModel().create_or_update_setting(
674 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
675 672
676 673 post_url = route_path('admin_settings_issuetracker_update')
677 674 post_data = {
678 'new_pattern_pattern_0': pattern,
679 'new_pattern_url_0': 'https://url',
675 'new_pattern_pattern_0': old_pattern,
676 'new_pattern_url_0': 'http://url',
680 677 'new_pattern_prefix_0': 'prefix',
681 678 'new_pattern_description_0': 'description',
679
680 'csrf_token': csrf_token
681 }
682 self.app.post(post_url, post_data, status=302)
683
684 new_pattern = 'issuetracker_pat1_edited'
685 self.new_uid = md5(new_pattern)
686
687 post_url = route_path('admin_settings_issuetracker_update')
688 post_data = {
689 'new_pattern_pattern_{}'.format(old_uid): new_pattern,
690 'new_pattern_url_{}'.format(old_uid): 'https://url_edited',
691 'new_pattern_prefix_{}'.format(old_uid): 'prefix_edited',
692 'new_pattern_description_{}'.format(old_uid): 'description_edited',
682 693 'uid': old_uid,
683 694 'csrf_token': csrf_token
684 695 }
685 696 self.app.post(post_url, post_data, status=302)
697
686 698 settings = SettingsModel().get_all_settings()
687 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
699 assert settings[self.PATTERN_KEY+self.new_uid] == new_pattern
700 assert settings[self.DESC_KEY + self.new_uid] == 'description_edited'
688 701 assert self.PATTERN_KEY+old_uid not in settings
689 702
690 703 @request.addfinalizer
691 704 def cleanup():
705 IssueTrackerSettingsModel().delete_entries(old_uid)
692 706 IssueTrackerSettingsModel().delete_entries(self.new_uid)
693 707
694 708 def test_replace_issuetracker_pattern_description(
695 709 self, autologin_user, csrf_token, request, settings_util):
696 710 prefix = 'issuetracker'
697 711 pattern = 'issuetracker_pat'
698 712 self.uid = md5(pattern)
699 713 pattern_key = '_'.join([prefix, 'pat', self.uid])
700 714 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
701 715 desc_key = '_'.join([prefix, 'desc', self.uid])
702 716 rc_desc_key = '_'.join(['rhodecode', desc_key])
703 717 new_description = 'new_description'
704 718
705 719 settings_util.create_rhodecode_setting(
706 720 pattern_key, pattern, 'unicode', cleanup=False)
707 721 settings_util.create_rhodecode_setting(
708 722 desc_key, 'old description', 'unicode', cleanup=False)
709 723
710 724 post_url = route_path('admin_settings_issuetracker_update')
711 725 post_data = {
712 726 'new_pattern_pattern_0': pattern,
713 727 'new_pattern_url_0': 'https://url',
714 728 'new_pattern_prefix_0': 'prefix',
715 729 'new_pattern_description_0': new_description,
716 730 'uid': self.uid,
717 731 'csrf_token': csrf_token
718 732 }
719 733 self.app.post(post_url, post_data, status=302)
720 734 settings = SettingsModel().get_all_settings()
721 735 assert settings[rc_pattern_key] == pattern
722 736 assert settings[rc_desc_key] == new_description
723 737
724 738 @request.addfinalizer
725 739 def cleanup():
726 740 IssueTrackerSettingsModel().delete_entries(self.uid)
727 741
728 742 def test_delete_issuetracker_pattern(
729 743 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
730 pattern = 'issuetracker_pat'
731 uid = md5(pattern)
732 settings_util.create_rhodecode_setting(
733 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
744
745 old_pattern = 'issuetracker_pat_deleted'
746 old_uid = md5(old_pattern)
747
748 post_url = route_path('admin_settings_issuetracker_update')
749 post_data = {
750 'new_pattern_pattern_0': old_pattern,
751 'new_pattern_url_0': 'http://url',
752 'new_pattern_prefix_0': 'prefix',
753 'new_pattern_description_0': 'description',
754
755 'csrf_token': csrf_token
756 }
757 self.app.post(post_url, post_data, status=302)
734 758
735 759 post_url = route_path('admin_settings_issuetracker_delete')
736 760 post_data = {
737 'uid': uid,
761 'uid': old_uid,
738 762 'csrf_token': csrf_token
739 763 }
740 764 self.app.post(post_url, post_data, extra_environ=xhr_header, status=200)
741 765 settings = SettingsModel().get_all_settings()
742 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
766 assert self.PATTERN_KEY+old_uid not in settings
767 assert self.DESC_KEY + old_uid not in settings
@@ -1,924 +1,924 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 re
23 23 import hashlib
24 24 import logging
25 25 import time
26 26 from collections import namedtuple
27 27 from functools import wraps
28 28 import bleach
29 29 from pyramid.threadlocal import get_current_request, get_current_registry
30 30
31 31 from rhodecode.lib import rc_cache
32 32 from rhodecode.lib.utils2 import (
33 33 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
34 34 from rhodecode.lib.vcs.backends import base
35 35 from rhodecode.lib.statsd_client import StatsdClient
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import (
38 38 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
39 39 from rhodecode.model.meta import Session
40 40
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 UiSetting = namedtuple(
46 46 'UiSetting', ['section', 'key', 'value', 'active'])
47 47
48 48 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
49 49
50 50
51 51 class SettingNotFound(Exception):
52 52 def __init__(self, setting_id):
53 53 msg = 'Setting `{}` is not found'.format(setting_id)
54 54 super(SettingNotFound, self).__init__(msg)
55 55
56 56
57 57 class SettingsModel(BaseModel):
58 58 BUILTIN_HOOKS = (
59 59 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
60 60 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
61 61 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
62 62 RhodeCodeUi.HOOK_PUSH_KEY,)
63 63 HOOKS_SECTION = 'hooks'
64 64
65 65 def __init__(self, sa=None, repo=None):
66 66 self.repo = repo
67 67 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
68 68 self.SettingsDbModel = (
69 69 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
70 70 super(SettingsModel, self).__init__(sa)
71 71
72 72 def get_ui_by_key(self, key):
73 73 q = self.UiDbModel.query()
74 74 q = q.filter(self.UiDbModel.ui_key == key)
75 75 q = self._filter_by_repo(RepoRhodeCodeUi, q)
76 76 return q.scalar()
77 77
78 78 def get_ui_by_section(self, section):
79 79 q = self.UiDbModel.query()
80 80 q = q.filter(self.UiDbModel.ui_section == section)
81 81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 82 return q.all()
83 83
84 84 def get_ui_by_section_and_key(self, section, key):
85 85 q = self.UiDbModel.query()
86 86 q = q.filter(self.UiDbModel.ui_section == section)
87 87 q = q.filter(self.UiDbModel.ui_key == key)
88 88 q = self._filter_by_repo(RepoRhodeCodeUi, q)
89 89 return q.scalar()
90 90
91 91 def get_ui(self, section=None, key=None):
92 92 q = self.UiDbModel.query()
93 93 q = self._filter_by_repo(RepoRhodeCodeUi, q)
94 94
95 95 if section:
96 96 q = q.filter(self.UiDbModel.ui_section == section)
97 97 if key:
98 98 q = q.filter(self.UiDbModel.ui_key == key)
99 99
100 100 # TODO: mikhail: add caching
101 101 result = [
102 102 UiSetting(
103 103 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
104 104 value=safe_str(r.ui_value), active=r.ui_active
105 105 )
106 106 for r in q.all()
107 107 ]
108 108 return result
109 109
110 110 def get_builtin_hooks(self):
111 111 q = self.UiDbModel.query()
112 112 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
113 113 return self._get_hooks(q)
114 114
115 115 def get_custom_hooks(self):
116 116 q = self.UiDbModel.query()
117 117 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
118 118 return self._get_hooks(q)
119 119
120 120 def create_ui_section_value(self, section, val, key=None, active=True):
121 121 new_ui = self.UiDbModel()
122 122 new_ui.ui_section = section
123 123 new_ui.ui_value = val
124 124 new_ui.ui_active = active
125 125
126 126 repository_id = ''
127 127 if self.repo:
128 128 repo = self._get_repo(self.repo)
129 129 repository_id = repo.repo_id
130 130 new_ui.repository_id = repository_id
131 131
132 132 if not key:
133 133 # keys are unique so they need appended info
134 134 if self.repo:
135 135 key = hashlib.sha1(
136 136 '{}{}{}'.format(section, val, repository_id)).hexdigest()
137 137 else:
138 138 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
139 139
140 140 new_ui.ui_key = key
141 141
142 142 Session().add(new_ui)
143 143 return new_ui
144 144
145 145 def create_or_update_hook(self, key, value):
146 146 ui = (
147 147 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
148 148 self.UiDbModel())
149 149 ui.ui_section = self.HOOKS_SECTION
150 150 ui.ui_active = True
151 151 ui.ui_key = key
152 152 ui.ui_value = value
153 153
154 154 if self.repo:
155 155 repo = self._get_repo(self.repo)
156 156 repository_id = repo.repo_id
157 157 ui.repository_id = repository_id
158 158
159 159 Session().add(ui)
160 160 return ui
161 161
162 162 def delete_ui(self, id_):
163 163 ui = self.UiDbModel.get(id_)
164 164 if not ui:
165 165 raise SettingNotFound(id_)
166 166 Session().delete(ui)
167 167
168 168 def get_setting_by_name(self, name):
169 169 q = self._get_settings_query()
170 170 q = q.filter(self.SettingsDbModel.app_settings_name == name)
171 171 return q.scalar()
172 172
173 173 def create_or_update_setting(
174 174 self, name, val=Optional(''), type_=Optional('unicode')):
175 175 """
176 176 Creates or updates RhodeCode setting. If updates is triggered it will
177 only update parameters that are explicityl set Optional instance will
177 only update parameters that are explicitly set Optional instance will
178 178 be skipped
179 179
180 180 :param name:
181 181 :param val:
182 182 :param type_:
183 183 :return:
184 184 """
185 185
186 186 res = self.get_setting_by_name(name)
187 187 repo = self._get_repo(self.repo) if self.repo else None
188 188
189 189 if not res:
190 190 val = Optional.extract(val)
191 191 type_ = Optional.extract(type_)
192 192
193 193 args = (
194 194 (repo.repo_id, name, val, type_)
195 195 if repo else (name, val, type_))
196 196 res = self.SettingsDbModel(*args)
197 197
198 198 else:
199 199 if self.repo:
200 200 res.repository_id = repo.repo_id
201 201
202 202 res.app_settings_name = name
203 203 if not isinstance(type_, Optional):
204 204 # update if set
205 205 res.app_settings_type = type_
206 206 if not isinstance(val, Optional):
207 207 # update if set
208 208 res.app_settings_value = val
209 209
210 210 Session().add(res)
211 211 return res
212 212
213 213 def get_cache_region(self):
214 214 repo = self._get_repo(self.repo) if self.repo else None
215 215 cache_key = "repo.{}".format(repo.repo_id) if repo else "general_settings"
216 216 cache_namespace_uid = 'cache_settings.{}'.format(cache_key)
217 217 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
218 218 return region, cache_key
219 219
220 220 def invalidate_settings_cache(self):
221 221 from rhodecode.authentication.base import get_authn_registry
222 222
223 223 region, cache_key = self.get_cache_region()
224 224 log.debug('Invalidation cache region %s for cache_key: %s', region, cache_key)
225 225 region.invalidate()
226 226 registry = get_current_registry()
227 227 if registry:
228 228 authn_registry = get_authn_registry(registry)
229 229 if authn_registry:
230 230 authn_registry.invalidate_plugins_for_auth()
231 231
232 232 def get_all_settings(self, cache=False, from_request=True):
233 233 # defines if we use GLOBAL, or PER_REPO
234 234 repo = self._get_repo(self.repo) if self.repo else None
235 235
236 236 # initially try the requests context, this is the fastest
237 237 # we only fetch global config
238 238 if from_request:
239 239 request = get_current_request()
240 240
241 241 if request and not repo and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
242 242 rc_config = request.call_context.rc_config
243 243 if rc_config:
244 244 return rc_config
245 245
246 246 region, cache_key = self.get_cache_region()
247 247
248 248 @region.conditional_cache_on_arguments(condition=cache)
249 249 def _get_all_settings(name, key):
250 250 q = self._get_settings_query()
251 251 if not q:
252 252 raise Exception('Could not get application settings !')
253 253
254 254 settings = {
255 255 'rhodecode_' + res.app_settings_name: res.app_settings_value
256 256 for res in q
257 257 }
258 258 return settings
259 259
260 260 start = time.time()
261 261 result = _get_all_settings('rhodecode_settings', cache_key)
262 262 compute_time = time.time() - start
263 263
264 264 statsd = StatsdClient.statsd
265 265 if statsd:
266 266 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
267 267 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
268 268 use_decimals=False)
269 269
270 log.debug('Fetching app settings for key: %s took: %.4fs', cache_key, compute_time)
270 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
271 271
272 272 return result
273 273
274 274 def get_auth_settings(self):
275 275 q = self._get_settings_query()
276 276 q = q.filter(
277 277 self.SettingsDbModel.app_settings_name.startswith('auth_'))
278 278 rows = q.all()
279 279 auth_settings = {
280 280 row.app_settings_name: row.app_settings_value for row in rows}
281 281 return auth_settings
282 282
283 283 def get_auth_plugins(self):
284 284 auth_plugins = self.get_setting_by_name("auth_plugins")
285 285 return auth_plugins.app_settings_value
286 286
287 287 def get_default_repo_settings(self, strip_prefix=False):
288 288 q = self._get_settings_query()
289 289 q = q.filter(
290 290 self.SettingsDbModel.app_settings_name.startswith('default_'))
291 291 rows = q.all()
292 292
293 293 result = {}
294 294 for row in rows:
295 295 key = row.app_settings_name
296 296 if strip_prefix:
297 297 key = remove_prefix(key, prefix='default_')
298 298 result.update({key: row.app_settings_value})
299 299 return result
300 300
301 301 def get_repo(self):
302 302 repo = self._get_repo(self.repo)
303 303 if not repo:
304 304 raise Exception(
305 305 'Repository `{}` cannot be found inside the database'.format(
306 306 self.repo))
307 307 return repo
308 308
309 309 def _filter_by_repo(self, model, query):
310 310 if self.repo:
311 311 repo = self.get_repo()
312 312 query = query.filter(model.repository_id == repo.repo_id)
313 313 return query
314 314
315 315 def _get_hooks(self, query):
316 316 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
317 317 query = self._filter_by_repo(RepoRhodeCodeUi, query)
318 318 return query.all()
319 319
320 320 def _get_settings_query(self):
321 321 q = self.SettingsDbModel.query()
322 322 return self._filter_by_repo(RepoRhodeCodeSetting, q)
323 323
324 324 def list_enabled_social_plugins(self, settings):
325 325 enabled = []
326 326 for plug in SOCIAL_PLUGINS_LIST:
327 327 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
328 328 )):
329 329 enabled.append(plug)
330 330 return enabled
331 331
332 332
333 333 def assert_repo_settings(func):
334 334 @wraps(func)
335 335 def _wrapper(self, *args, **kwargs):
336 336 if not self.repo_settings:
337 337 raise Exception('Repository is not specified')
338 338 return func(self, *args, **kwargs)
339 339 return _wrapper
340 340
341 341
342 342 class IssueTrackerSettingsModel(object):
343 343 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
344 344 SETTINGS_PREFIX = 'issuetracker_'
345 345
346 346 def __init__(self, sa=None, repo=None):
347 347 self.global_settings = SettingsModel(sa=sa)
348 348 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
349 349
350 350 @property
351 351 def inherit_global_settings(self):
352 352 if not self.repo_settings:
353 353 return True
354 354 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
355 355 return setting.app_settings_value if setting else True
356 356
357 357 @inherit_global_settings.setter
358 358 def inherit_global_settings(self, value):
359 359 if self.repo_settings:
360 360 settings = self.repo_settings.create_or_update_setting(
361 361 self.INHERIT_SETTINGS, value, type_='bool')
362 362 Session().add(settings)
363 363
364 364 def _get_keyname(self, key, uid, prefix=''):
365 365 return '{0}{1}{2}_{3}'.format(
366 366 prefix, self.SETTINGS_PREFIX, key, uid)
367 367
368 368 def _make_dict_for_settings(self, qs):
369 369 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
370 370
371 371 issuetracker_entries = {}
372 372 # create keys
373 373 for k, v in qs.items():
374 374 if k.startswith(prefix_match):
375 375 uid = k[len(prefix_match):]
376 376 issuetracker_entries[uid] = None
377 377
378 378 def url_cleaner(input_str):
379 379 input_str = input_str.replace('"', '').replace("'", '')
380 380 input_str = bleach.clean(input_str, strip=True)
381 381 return input_str
382 382
383 383 # populate
384 384 for uid in issuetracker_entries:
385 385 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
386 386
387 387 pat = qs.get(self._get_keyname('pat', uid, 'rhodecode_'))
388 388 try:
389 389 pat_compiled = re.compile(r'%s' % pat)
390 390 except re.error:
391 391 pat_compiled = None
392 392
393 393 issuetracker_entries[uid] = AttributeDict({
394 394 'pat': pat,
395 395 'pat_compiled': pat_compiled,
396 396 'url': url_cleaner(
397 397 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
398 398 'pref': bleach.clean(
399 399 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
400 400 'desc': qs.get(
401 401 self._get_keyname('desc', uid, 'rhodecode_')),
402 402 })
403 403
404 404 return issuetracker_entries
405 405
406 406 def get_global_settings(self, cache=False):
407 407 """
408 408 Returns list of global issue tracker settings
409 409 """
410 410 defaults = self.global_settings.get_all_settings(cache=cache)
411 411 settings = self._make_dict_for_settings(defaults)
412 412 return settings
413 413
414 414 def get_repo_settings(self, cache=False):
415 415 """
416 416 Returns list of issue tracker settings per repository
417 417 """
418 418 if not self.repo_settings:
419 419 raise Exception('Repository is not specified')
420 420 all_settings = self.repo_settings.get_all_settings(cache=cache)
421 421 settings = self._make_dict_for_settings(all_settings)
422 422 return settings
423 423
424 424 def get_settings(self, cache=False):
425 425 if self.inherit_global_settings:
426 426 return self.get_global_settings(cache=cache)
427 427 else:
428 428 return self.get_repo_settings(cache=cache)
429 429
430 430 def delete_entries(self, uid):
431 431 if self.repo_settings:
432 432 all_patterns = self.get_repo_settings()
433 433 settings_model = self.repo_settings
434 434 else:
435 435 all_patterns = self.get_global_settings()
436 436 settings_model = self.global_settings
437 437 entries = all_patterns.get(uid, [])
438 438
439 439 for del_key in entries:
440 440 setting_name = self._get_keyname(del_key, uid)
441 441 entry = settings_model.get_setting_by_name(setting_name)
442 442 if entry:
443 443 Session().delete(entry)
444 444
445 445 Session().commit()
446 446
447 447 def create_or_update_setting(
448 448 self, name, val=Optional(''), type_=Optional('unicode')):
449 449 if self.repo_settings:
450 450 setting = self.repo_settings.create_or_update_setting(
451 451 name, val, type_)
452 452 else:
453 453 setting = self.global_settings.create_or_update_setting(
454 454 name, val, type_)
455 455 return setting
456 456
457 457
458 458 class VcsSettingsModel(object):
459 459
460 460 INHERIT_SETTINGS = 'inherit_vcs_settings'
461 461 GENERAL_SETTINGS = (
462 462 'use_outdated_comments',
463 463 'pr_merge_enabled',
464 464 'hg_use_rebase_for_merging',
465 465 'hg_close_branch_before_merging',
466 466 'git_use_rebase_for_merging',
467 467 'git_close_branch_before_merging',
468 468 'diff_cache',
469 469 )
470 470
471 471 HOOKS_SETTINGS = (
472 472 ('hooks', 'changegroup.repo_size'),
473 473 ('hooks', 'changegroup.push_logger'),
474 474 ('hooks', 'outgoing.pull_logger'),
475 475 )
476 476 HG_SETTINGS = (
477 477 ('extensions', 'largefiles'),
478 478 ('phases', 'publish'),
479 479 ('extensions', 'evolve'),
480 480 ('extensions', 'topic'),
481 481 ('experimental', 'evolution'),
482 482 ('experimental', 'evolution.exchange'),
483 483 )
484 484 GIT_SETTINGS = (
485 485 ('vcs_git_lfs', 'enabled'),
486 486 )
487 487 GLOBAL_HG_SETTINGS = (
488 488 ('extensions', 'largefiles'),
489 489 ('largefiles', 'usercache'),
490 490 ('phases', 'publish'),
491 491 ('extensions', 'hgsubversion'),
492 492 ('extensions', 'evolve'),
493 493 ('extensions', 'topic'),
494 494 ('experimental', 'evolution'),
495 495 ('experimental', 'evolution.exchange'),
496 496 )
497 497
498 498 GLOBAL_GIT_SETTINGS = (
499 499 ('vcs_git_lfs', 'enabled'),
500 500 ('vcs_git_lfs', 'store_location')
501 501 )
502 502
503 503 GLOBAL_SVN_SETTINGS = (
504 504 ('vcs_svn_proxy', 'http_requests_enabled'),
505 505 ('vcs_svn_proxy', 'http_server_url')
506 506 )
507 507
508 508 SVN_BRANCH_SECTION = 'vcs_svn_branch'
509 509 SVN_TAG_SECTION = 'vcs_svn_tag'
510 510 SSL_SETTING = ('web', 'push_ssl')
511 511 PATH_SETTING = ('paths', '/')
512 512
513 513 def __init__(self, sa=None, repo=None):
514 514 self.global_settings = SettingsModel(sa=sa)
515 515 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
516 516 self._ui_settings = (
517 517 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
518 518 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
519 519
520 520 @property
521 521 @assert_repo_settings
522 522 def inherit_global_settings(self):
523 523 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
524 524 return setting.app_settings_value if setting else True
525 525
526 526 @inherit_global_settings.setter
527 527 @assert_repo_settings
528 528 def inherit_global_settings(self, value):
529 529 self.repo_settings.create_or_update_setting(
530 530 self.INHERIT_SETTINGS, value, type_='bool')
531 531
532 532 def get_global_svn_branch_patterns(self):
533 533 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
534 534
535 535 @assert_repo_settings
536 536 def get_repo_svn_branch_patterns(self):
537 537 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
538 538
539 539 def get_global_svn_tag_patterns(self):
540 540 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
541 541
542 542 @assert_repo_settings
543 543 def get_repo_svn_tag_patterns(self):
544 544 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
545 545
546 546 def get_global_settings(self):
547 547 return self._collect_all_settings(global_=True)
548 548
549 549 @assert_repo_settings
550 550 def get_repo_settings(self):
551 551 return self._collect_all_settings(global_=False)
552 552
553 553 @assert_repo_settings
554 554 def get_repo_settings_inherited(self):
555 555 global_settings = self.get_global_settings()
556 556 global_settings.update(self.get_repo_settings())
557 557 return global_settings
558 558
559 559 @assert_repo_settings
560 560 def create_or_update_repo_settings(
561 561 self, data, inherit_global_settings=False):
562 562 from rhodecode.model.scm import ScmModel
563 563
564 564 self.inherit_global_settings = inherit_global_settings
565 565
566 566 repo = self.repo_settings.get_repo()
567 567 if not inherit_global_settings:
568 568 if repo.repo_type == 'svn':
569 569 self.create_repo_svn_settings(data)
570 570 else:
571 571 self.create_or_update_repo_hook_settings(data)
572 572 self.create_or_update_repo_pr_settings(data)
573 573
574 574 if repo.repo_type == 'hg':
575 575 self.create_or_update_repo_hg_settings(data)
576 576
577 577 if repo.repo_type == 'git':
578 578 self.create_or_update_repo_git_settings(data)
579 579
580 580 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
581 581
582 582 @assert_repo_settings
583 583 def create_or_update_repo_hook_settings(self, data):
584 584 for section, key in self.HOOKS_SETTINGS:
585 585 data_key = self._get_form_ui_key(section, key)
586 586 if data_key not in data:
587 587 raise ValueError(
588 588 'The given data does not contain {} key'.format(data_key))
589 589
590 590 active = data.get(data_key)
591 591 repo_setting = self.repo_settings.get_ui_by_section_and_key(
592 592 section, key)
593 593 if not repo_setting:
594 594 global_setting = self.global_settings.\
595 595 get_ui_by_section_and_key(section, key)
596 596 self.repo_settings.create_ui_section_value(
597 597 section, global_setting.ui_value, key=key, active=active)
598 598 else:
599 599 repo_setting.ui_active = active
600 600 Session().add(repo_setting)
601 601
602 602 def update_global_hook_settings(self, data):
603 603 for section, key in self.HOOKS_SETTINGS:
604 604 data_key = self._get_form_ui_key(section, key)
605 605 if data_key not in data:
606 606 raise ValueError(
607 607 'The given data does not contain {} key'.format(data_key))
608 608 active = data.get(data_key)
609 609 repo_setting = self.global_settings.get_ui_by_section_and_key(
610 610 section, key)
611 611 repo_setting.ui_active = active
612 612 Session().add(repo_setting)
613 613
614 614 @assert_repo_settings
615 615 def create_or_update_repo_pr_settings(self, data):
616 616 return self._create_or_update_general_settings(
617 617 self.repo_settings, data)
618 618
619 619 def create_or_update_global_pr_settings(self, data):
620 620 return self._create_or_update_general_settings(
621 621 self.global_settings, data)
622 622
623 623 @assert_repo_settings
624 624 def create_repo_svn_settings(self, data):
625 625 return self._create_svn_settings(self.repo_settings, data)
626 626
627 627 def _set_evolution(self, settings, is_enabled):
628 628 if is_enabled:
629 629 # if evolve is active set evolution=all
630 630
631 631 self._create_or_update_ui(
632 632 settings, *('experimental', 'evolution'), value='all',
633 633 active=True)
634 634 self._create_or_update_ui(
635 635 settings, *('experimental', 'evolution.exchange'), value='yes',
636 636 active=True)
637 637 # if evolve is active set topics server support
638 638 self._create_or_update_ui(
639 639 settings, *('extensions', 'topic'), value='',
640 640 active=True)
641 641
642 642 else:
643 643 self._create_or_update_ui(
644 644 settings, *('experimental', 'evolution'), value='',
645 645 active=False)
646 646 self._create_or_update_ui(
647 647 settings, *('experimental', 'evolution.exchange'), value='no',
648 648 active=False)
649 649 self._create_or_update_ui(
650 650 settings, *('extensions', 'topic'), value='',
651 651 active=False)
652 652
653 653 @assert_repo_settings
654 654 def create_or_update_repo_hg_settings(self, data):
655 655 largefiles, phases, evolve = \
656 656 self.HG_SETTINGS[:3]
657 657 largefiles_key, phases_key, evolve_key = \
658 658 self._get_settings_keys(self.HG_SETTINGS[:3], data)
659 659
660 660 self._create_or_update_ui(
661 661 self.repo_settings, *largefiles, value='',
662 662 active=data[largefiles_key])
663 663 self._create_or_update_ui(
664 664 self.repo_settings, *evolve, value='',
665 665 active=data[evolve_key])
666 666 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
667 667
668 668 self._create_or_update_ui(
669 669 self.repo_settings, *phases, value=safe_str(data[phases_key]))
670 670
671 671 def create_or_update_global_hg_settings(self, data):
672 672 largefiles, largefiles_store, phases, hgsubversion, evolve \
673 673 = self.GLOBAL_HG_SETTINGS[:5]
674 674 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
675 675 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
676 676
677 677 self._create_or_update_ui(
678 678 self.global_settings, *largefiles, value='',
679 679 active=data[largefiles_key])
680 680 self._create_or_update_ui(
681 681 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
682 682 self._create_or_update_ui(
683 683 self.global_settings, *phases, value=safe_str(data[phases_key]))
684 684 self._create_or_update_ui(
685 685 self.global_settings, *hgsubversion, active=data[subversion_key])
686 686 self._create_or_update_ui(
687 687 self.global_settings, *evolve, value='',
688 688 active=data[evolve_key])
689 689 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
690 690
691 691 def create_or_update_repo_git_settings(self, data):
692 692 # NOTE(marcink): # comma makes unpack work properly
693 693 lfs_enabled, \
694 694 = self.GIT_SETTINGS
695 695
696 696 lfs_enabled_key, \
697 697 = self._get_settings_keys(self.GIT_SETTINGS, data)
698 698
699 699 self._create_or_update_ui(
700 700 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
701 701 active=data[lfs_enabled_key])
702 702
703 703 def create_or_update_global_git_settings(self, data):
704 704 lfs_enabled, lfs_store_location \
705 705 = self.GLOBAL_GIT_SETTINGS
706 706 lfs_enabled_key, lfs_store_location_key \
707 707 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
708 708
709 709 self._create_or_update_ui(
710 710 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
711 711 active=data[lfs_enabled_key])
712 712 self._create_or_update_ui(
713 713 self.global_settings, *lfs_store_location,
714 714 value=data[lfs_store_location_key])
715 715
716 716 def create_or_update_global_svn_settings(self, data):
717 717 # branch/tags patterns
718 718 self._create_svn_settings(self.global_settings, data)
719 719
720 720 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
721 721 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
722 722 self.GLOBAL_SVN_SETTINGS, data)
723 723
724 724 self._create_or_update_ui(
725 725 self.global_settings, *http_requests_enabled,
726 726 value=safe_str(data[http_requests_enabled_key]))
727 727 self._create_or_update_ui(
728 728 self.global_settings, *http_server_url,
729 729 value=data[http_server_url_key])
730 730
731 731 def update_global_ssl_setting(self, value):
732 732 self._create_or_update_ui(
733 733 self.global_settings, *self.SSL_SETTING, value=value)
734 734
735 735 def update_global_path_setting(self, value):
736 736 self._create_or_update_ui(
737 737 self.global_settings, *self.PATH_SETTING, value=value)
738 738
739 739 @assert_repo_settings
740 740 def delete_repo_svn_pattern(self, id_):
741 741 ui = self.repo_settings.UiDbModel.get(id_)
742 742 if ui and ui.repository.repo_name == self.repo_settings.repo:
743 743 # only delete if it's the same repo as initialized settings
744 744 self.repo_settings.delete_ui(id_)
745 745 else:
746 746 # raise error as if we wouldn't find this option
747 747 self.repo_settings.delete_ui(-1)
748 748
749 749 def delete_global_svn_pattern(self, id_):
750 750 self.global_settings.delete_ui(id_)
751 751
752 752 @assert_repo_settings
753 753 def get_repo_ui_settings(self, section=None, key=None):
754 754 global_uis = self.global_settings.get_ui(section, key)
755 755 repo_uis = self.repo_settings.get_ui(section, key)
756 756
757 757 filtered_repo_uis = self._filter_ui_settings(repo_uis)
758 758 filtered_repo_uis_keys = [
759 759 (s.section, s.key) for s in filtered_repo_uis]
760 760
761 761 def _is_global_ui_filtered(ui):
762 762 return (
763 763 (ui.section, ui.key) in filtered_repo_uis_keys
764 764 or ui.section in self._svn_sections)
765 765
766 766 filtered_global_uis = [
767 767 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
768 768
769 769 return filtered_global_uis + filtered_repo_uis
770 770
771 771 def get_global_ui_settings(self, section=None, key=None):
772 772 return self.global_settings.get_ui(section, key)
773 773
774 774 def get_ui_settings_as_config_obj(self, section=None, key=None):
775 775 config = base.Config()
776 776
777 777 ui_settings = self.get_ui_settings(section=section, key=key)
778 778
779 779 for entry in ui_settings:
780 780 config.set(entry.section, entry.key, entry.value)
781 781
782 782 return config
783 783
784 784 def get_ui_settings(self, section=None, key=None):
785 785 if not self.repo_settings or self.inherit_global_settings:
786 786 return self.get_global_ui_settings(section, key)
787 787 else:
788 788 return self.get_repo_ui_settings(section, key)
789 789
790 790 def get_svn_patterns(self, section=None):
791 791 if not self.repo_settings:
792 792 return self.get_global_ui_settings(section)
793 793 else:
794 794 return self.get_repo_ui_settings(section)
795 795
796 796 @assert_repo_settings
797 797 def get_repo_general_settings(self):
798 798 global_settings = self.global_settings.get_all_settings()
799 799 repo_settings = self.repo_settings.get_all_settings()
800 800 filtered_repo_settings = self._filter_general_settings(repo_settings)
801 801 global_settings.update(filtered_repo_settings)
802 802 return global_settings
803 803
804 804 def get_global_general_settings(self):
805 805 return self.global_settings.get_all_settings()
806 806
807 807 def get_general_settings(self):
808 808 if not self.repo_settings or self.inherit_global_settings:
809 809 return self.get_global_general_settings()
810 810 else:
811 811 return self.get_repo_general_settings()
812 812
813 813 def get_repos_location(self):
814 814 return self.global_settings.get_ui_by_key('/').ui_value
815 815
816 816 def _filter_ui_settings(self, settings):
817 817 filtered_settings = [
818 818 s for s in settings if self._should_keep_setting(s)]
819 819 return filtered_settings
820 820
821 821 def _should_keep_setting(self, setting):
822 822 keep = (
823 823 (setting.section, setting.key) in self._ui_settings or
824 824 setting.section in self._svn_sections)
825 825 return keep
826 826
827 827 def _filter_general_settings(self, settings):
828 828 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
829 829 return {
830 830 k: settings[k]
831 831 for k in settings if k in keys}
832 832
833 833 def _collect_all_settings(self, global_=False):
834 834 settings = self.global_settings if global_ else self.repo_settings
835 835 result = {}
836 836
837 837 for section, key in self._ui_settings:
838 838 ui = settings.get_ui_by_section_and_key(section, key)
839 839 result_key = self._get_form_ui_key(section, key)
840 840
841 841 if ui:
842 842 if section in ('hooks', 'extensions'):
843 843 result[result_key] = ui.ui_active
844 844 elif result_key in ['vcs_git_lfs_enabled']:
845 845 result[result_key] = ui.ui_active
846 846 else:
847 847 result[result_key] = ui.ui_value
848 848
849 849 for name in self.GENERAL_SETTINGS:
850 850 setting = settings.get_setting_by_name(name)
851 851 if setting:
852 852 result_key = 'rhodecode_{}'.format(name)
853 853 result[result_key] = setting.app_settings_value
854 854
855 855 return result
856 856
857 857 def _get_form_ui_key(self, section, key):
858 858 return '{section}_{key}'.format(
859 859 section=section, key=key.replace('.', '_'))
860 860
861 861 def _create_or_update_ui(
862 862 self, settings, section, key, value=None, active=None):
863 863 ui = settings.get_ui_by_section_and_key(section, key)
864 864 if not ui:
865 865 active = True if active is None else active
866 866 settings.create_ui_section_value(
867 867 section, value, key=key, active=active)
868 868 else:
869 869 if active is not None:
870 870 ui.ui_active = active
871 871 if value is not None:
872 872 ui.ui_value = value
873 873 Session().add(ui)
874 874
875 875 def _create_svn_settings(self, settings, data):
876 876 svn_settings = {
877 877 'new_svn_branch': self.SVN_BRANCH_SECTION,
878 878 'new_svn_tag': self.SVN_TAG_SECTION
879 879 }
880 880 for key in svn_settings:
881 881 if data.get(key):
882 882 settings.create_ui_section_value(svn_settings[key], data[key])
883 883
884 884 def _create_or_update_general_settings(self, settings, data):
885 885 for name in self.GENERAL_SETTINGS:
886 886 data_key = 'rhodecode_{}'.format(name)
887 887 if data_key not in data:
888 888 raise ValueError(
889 889 'The given data does not contain {} key'.format(data_key))
890 890 setting = settings.create_or_update_setting(
891 891 name, data[data_key], 'bool')
892 892 Session().add(setting)
893 893
894 894 def _get_settings_keys(self, settings, data):
895 895 data_keys = [self._get_form_ui_key(*s) for s in settings]
896 896 for data_key in data_keys:
897 897 if data_key not in data:
898 898 raise ValueError(
899 899 'The given data does not contain {} key'.format(data_key))
900 900 return data_keys
901 901
902 902 def create_largeobjects_dirs_if_needed(self, repo_store_path):
903 903 """
904 904 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
905 905 does a repository scan if enabled in the settings.
906 906 """
907 907
908 908 from rhodecode.lib.vcs.backends.hg import largefiles_store
909 909 from rhodecode.lib.vcs.backends.git import lfs_store
910 910
911 911 paths = [
912 912 largefiles_store(repo_store_path),
913 913 lfs_store(repo_store_path)]
914 914
915 915 for path in paths:
916 916 if os.path.isdir(path):
917 917 continue
918 918 if os.path.isfile(path):
919 919 continue
920 920 # not a file nor dir, we try to create it
921 921 try:
922 922 os.makedirs(path)
923 923 except Exception:
924 924 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now