##// END OF EJS Templates
clone-urls: allow custom clone by id template.
milka -
r4629:35fb8d8f stable
parent child Browse files
Show More
@@ -1,719 +1,720 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
22 22 import logging
23 23 import collections
24 24
25 25 import datetime
26 26 import formencode
27 27 import formencode.htmlfill
28 28
29 29 import rhodecode
30 30
31 31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 from rhodecode.apps._base import BaseAppView
36 36 from rhodecode.apps._base.navigation import navigation_list
37 37 from rhodecode.apps.svn_support.config_keys import generate_config
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 41 from rhodecode.lib.celerylib import tasks, run_task
42 42 from rhodecode.lib.utils import repo2db_mapper
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 44 from rhodecode.lib.index import searcher_from_config
45 45
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 49 LabsSettingsForm, IssueTrackerPatternsForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52
53 53 from rhodecode.model.scm import ScmModel
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.settings import (
57 57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 58 SettingsModel)
59 59
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminSettingsView(BaseAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 c.labs_active = str2bool(
69 69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 70 c.navlist = navigation_list(self.request)
71 71 return c
72 72
73 73 @classmethod
74 74 def _get_ui_settings(cls):
75 75 ret = RhodeCodeUi.query().all()
76 76
77 77 if not ret:
78 78 raise Exception('Could not get application ui settings !')
79 79 settings = {}
80 80 for each in ret:
81 81 k = each.ui_key
82 82 v = each.ui_value
83 83 if k == '/':
84 84 k = 'root_path'
85 85
86 86 if k in ['push_ssl', 'publish', 'enabled']:
87 87 v = str2bool(v)
88 88
89 89 if k.find('.') != -1:
90 90 k = k.replace('.', '_')
91 91
92 92 if each.ui_section in ['hooks', 'extensions']:
93 93 v = each.ui_active
94 94
95 95 settings[each.ui_section + '_' + k] = v
96 96 return settings
97 97
98 98 @classmethod
99 99 def _form_defaults(cls):
100 100 defaults = SettingsModel().get_all_settings()
101 101 defaults.update(cls._get_ui_settings())
102 102
103 103 defaults.update({
104 104 'new_svn_branch': '',
105 105 'new_svn_tag': '',
106 106 })
107 107 return defaults
108 108
109 109 @LoginRequired()
110 110 @HasPermissionAllDecorator('hg.admin')
111 111 def settings_vcs(self):
112 112 c = self.load_default_context()
113 113 c.active = 'vcs'
114 114 model = VcsSettingsModel()
115 115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
116 116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
117 117
118 118 settings = self.request.registry.settings
119 119 c.svn_proxy_generate_config = settings[generate_config]
120 120
121 121 defaults = self._form_defaults()
122 122
123 123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
124 124
125 125 data = render('rhodecode:templates/admin/settings/settings.mako',
126 126 self._get_template_context(c), self.request)
127 127 html = formencode.htmlfill.render(
128 128 data,
129 129 defaults=defaults,
130 130 encoding="UTF-8",
131 131 force_defaults=False
132 132 )
133 133 return Response(html)
134 134
135 135 @LoginRequired()
136 136 @HasPermissionAllDecorator('hg.admin')
137 137 @CSRFRequired()
138 138 def settings_vcs_update(self):
139 139 _ = self.request.translate
140 140 c = self.load_default_context()
141 141 c.active = 'vcs'
142 142
143 143 model = VcsSettingsModel()
144 144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
145 145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
146 146
147 147 settings = self.request.registry.settings
148 148 c.svn_proxy_generate_config = settings[generate_config]
149 149
150 150 application_form = ApplicationUiSettingsForm(self.request.translate)()
151 151
152 152 try:
153 153 form_result = application_form.to_python(dict(self.request.POST))
154 154 except formencode.Invalid as errors:
155 155 h.flash(
156 156 _("Some form inputs contain invalid data."),
157 157 category='error')
158 158 data = render('rhodecode:templates/admin/settings/settings.mako',
159 159 self._get_template_context(c), self.request)
160 160 html = formencode.htmlfill.render(
161 161 data,
162 162 defaults=errors.value,
163 163 errors=errors.error_dict or {},
164 164 prefix_error=False,
165 165 encoding="UTF-8",
166 166 force_defaults=False
167 167 )
168 168 return Response(html)
169 169
170 170 try:
171 171 if c.visual.allow_repo_location_change:
172 172 model.update_global_path_setting(form_result['paths_root_path'])
173 173
174 174 model.update_global_ssl_setting(form_result['web_push_ssl'])
175 175 model.update_global_hook_settings(form_result)
176 176
177 177 model.create_or_update_global_svn_settings(form_result)
178 178 model.create_or_update_global_hg_settings(form_result)
179 179 model.create_or_update_global_git_settings(form_result)
180 180 model.create_or_update_global_pr_settings(form_result)
181 181 except Exception:
182 182 log.exception("Exception while updating settings")
183 183 h.flash(_('Error occurred during updating '
184 184 'application settings'), category='error')
185 185 else:
186 186 Session().commit()
187 187 h.flash(_('Updated VCS settings'), category='success')
188 188 raise HTTPFound(h.route_path('admin_settings_vcs'))
189 189
190 190 data = render('rhodecode:templates/admin/settings/settings.mako',
191 191 self._get_template_context(c), self.request)
192 192 html = formencode.htmlfill.render(
193 193 data,
194 194 defaults=self._form_defaults(),
195 195 encoding="UTF-8",
196 196 force_defaults=False
197 197 )
198 198 return Response(html)
199 199
200 200 @LoginRequired()
201 201 @HasPermissionAllDecorator('hg.admin')
202 202 @CSRFRequired()
203 203 def settings_vcs_delete_svn_pattern(self):
204 204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
205 205 model = VcsSettingsModel()
206 206 try:
207 207 model.delete_global_svn_pattern(delete_pattern_id)
208 208 except SettingNotFound:
209 209 log.exception(
210 210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
211 211 raise HTTPNotFound()
212 212
213 213 Session().commit()
214 214 return True
215 215
216 216 @LoginRequired()
217 217 @HasPermissionAllDecorator('hg.admin')
218 218 def settings_mapping(self):
219 219 c = self.load_default_context()
220 220 c.active = 'mapping'
221 221
222 222 data = render('rhodecode:templates/admin/settings/settings.mako',
223 223 self._get_template_context(c), self.request)
224 224 html = formencode.htmlfill.render(
225 225 data,
226 226 defaults=self._form_defaults(),
227 227 encoding="UTF-8",
228 228 force_defaults=False
229 229 )
230 230 return Response(html)
231 231
232 232 @LoginRequired()
233 233 @HasPermissionAllDecorator('hg.admin')
234 234 @CSRFRequired()
235 235 def settings_mapping_update(self):
236 236 _ = self.request.translate
237 237 c = self.load_default_context()
238 238 c.active = 'mapping'
239 239 rm_obsolete = self.request.POST.get('destroy', False)
240 240 invalidate_cache = self.request.POST.get('invalidate', False)
241 241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
242 242
243 243 if invalidate_cache:
244 244 log.debug('invalidating all repositories cache')
245 245 for repo in Repository.get_all():
246 246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
247 247
248 248 filesystem_repos = ScmModel().repo_scan()
249 249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
250 250 PermissionModel().trigger_permission_flush()
251 251
252 252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
253 253 h.flash(_('Repositories successfully '
254 254 'rescanned added: %s ; removed: %s') %
255 255 (_repr(added), _repr(removed)),
256 256 category='success')
257 257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 258
259 259 @LoginRequired()
260 260 @HasPermissionAllDecorator('hg.admin')
261 261 def settings_global(self):
262 262 c = self.load_default_context()
263 263 c.active = 'global'
264 264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 265 .get_personal_group_name_pattern()
266 266
267 267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 268 self._get_template_context(c), self.request)
269 269 html = formencode.htmlfill.render(
270 270 data,
271 271 defaults=self._form_defaults(),
272 272 encoding="UTF-8",
273 273 force_defaults=False
274 274 )
275 275 return Response(html)
276 276
277 277 @LoginRequired()
278 278 @HasPermissionAllDecorator('hg.admin')
279 279 @CSRFRequired()
280 280 def settings_global_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283 c.active = 'global'
284 284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 285 .get_personal_group_name_pattern()
286 286 application_form = ApplicationSettingsForm(self.request.translate)()
287 287 try:
288 288 form_result = application_form.to_python(dict(self.request.POST))
289 289 except formencode.Invalid as errors:
290 290 h.flash(
291 291 _("Some form inputs contain invalid data."),
292 292 category='error')
293 293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 294 self._get_template_context(c), self.request)
295 295 html = formencode.htmlfill.render(
296 296 data,
297 297 defaults=errors.value,
298 298 errors=errors.error_dict or {},
299 299 prefix_error=False,
300 300 encoding="UTF-8",
301 301 force_defaults=False
302 302 )
303 303 return Response(html)
304 304
305 305 settings = [
306 306 ('title', 'rhodecode_title', 'unicode'),
307 307 ('realm', 'rhodecode_realm', 'unicode'),
308 308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 314 ]
315 315 try:
316 316 for setting, form_key, type_ in settings:
317 317 sett = SettingsModel().create_or_update_setting(
318 318 setting, form_result[form_key], type_)
319 319 Session().add(sett)
320 320
321 321 Session().commit()
322 322 SettingsModel().invalidate_settings_cache()
323 323 h.flash(_('Updated application settings'), category='success')
324 324 except Exception:
325 325 log.exception("Exception while updating application settings")
326 326 h.flash(
327 327 _('Error occurred during updating application settings'),
328 328 category='error')
329 329
330 330 raise HTTPFound(h.route_path('admin_settings_global'))
331 331
332 332 @LoginRequired()
333 333 @HasPermissionAllDecorator('hg.admin')
334 334 def settings_visual(self):
335 335 c = self.load_default_context()
336 336 c.active = 'visual'
337 337
338 338 data = render('rhodecode:templates/admin/settings/settings.mako',
339 339 self._get_template_context(c), self.request)
340 340 html = formencode.htmlfill.render(
341 341 data,
342 342 defaults=self._form_defaults(),
343 343 encoding="UTF-8",
344 344 force_defaults=False
345 345 )
346 346 return Response(html)
347 347
348 348 @LoginRequired()
349 349 @HasPermissionAllDecorator('hg.admin')
350 350 @CSRFRequired()
351 351 def settings_visual_update(self):
352 352 _ = self.request.translate
353 353 c = self.load_default_context()
354 354 c.active = 'visual'
355 355 application_form = ApplicationVisualisationForm(self.request.translate)()
356 356 try:
357 357 form_result = application_form.to_python(dict(self.request.POST))
358 358 except formencode.Invalid as errors:
359 359 h.flash(
360 360 _("Some form inputs contain invalid data."),
361 361 category='error')
362 362 data = render('rhodecode:templates/admin/settings/settings.mako',
363 363 self._get_template_context(c), self.request)
364 364 html = formencode.htmlfill.render(
365 365 data,
366 366 defaults=errors.value,
367 367 errors=errors.error_dict or {},
368 368 prefix_error=False,
369 369 encoding="UTF-8",
370 370 force_defaults=False
371 371 )
372 372 return Response(html)
373 373
374 374 try:
375 375 settings = [
376 376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
377 377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
378 378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
379 379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
380 380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
381 381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
382 382 ('show_version', 'rhodecode_show_version', 'bool'),
383 383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
384 384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
385 385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
386 386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
387 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
387 388 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
388 389 ('support_url', 'rhodecode_support_url', 'unicode'),
389 390 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
390 391 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
391 392 ]
392 393 for setting, form_key, type_ in settings:
393 394 sett = SettingsModel().create_or_update_setting(
394 395 setting, form_result[form_key], type_)
395 396 Session().add(sett)
396 397
397 398 Session().commit()
398 399 SettingsModel().invalidate_settings_cache()
399 400 h.flash(_('Updated visualisation settings'), category='success')
400 401 except Exception:
401 402 log.exception("Exception updating visualization settings")
402 403 h.flash(_('Error occurred during updating '
403 404 'visualisation settings'),
404 405 category='error')
405 406
406 407 raise HTTPFound(h.route_path('admin_settings_visual'))
407 408
408 409 @LoginRequired()
409 410 @HasPermissionAllDecorator('hg.admin')
410 411 def settings_issuetracker(self):
411 412 c = self.load_default_context()
412 413 c.active = 'issuetracker'
413 414 defaults = c.rc_config
414 415
415 416 entry_key = 'rhodecode_issuetracker_pat_'
416 417
417 418 c.issuetracker_entries = {}
418 419 for k, v in defaults.items():
419 420 if k.startswith(entry_key):
420 421 uid = k[len(entry_key):]
421 422 c.issuetracker_entries[uid] = None
422 423
423 424 for uid in c.issuetracker_entries:
424 425 c.issuetracker_entries[uid] = AttributeDict({
425 426 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
426 427 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
427 428 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
428 429 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
429 430 })
430 431
431 432 return self._get_template_context(c)
432 433
433 434 @LoginRequired()
434 435 @HasPermissionAllDecorator('hg.admin')
435 436 @CSRFRequired()
436 437 def settings_issuetracker_test(self):
437 438 error_container = []
438 439
439 440 urlified_commit = h.urlify_commit_message(
440 441 self.request.POST.get('test_text', ''),
441 442 'repo_group/test_repo1', error_container=error_container)
442 443 if error_container:
443 444 def converter(inp):
444 445 return h.html_escape(unicode(inp))
445 446
446 447 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
447 448
448 449 return urlified_commit
449 450
450 451 @LoginRequired()
451 452 @HasPermissionAllDecorator('hg.admin')
452 453 @CSRFRequired()
453 454 def settings_issuetracker_update(self):
454 455 _ = self.request.translate
455 456 self.load_default_context()
456 457 settings_model = IssueTrackerSettingsModel()
457 458
458 459 try:
459 460 form = IssueTrackerPatternsForm(self.request.translate)()
460 461 data = form.to_python(self.request.POST)
461 462 except formencode.Invalid as errors:
462 463 log.exception('Failed to add new pattern')
463 464 error = errors
464 465 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
465 466 category='error')
466 467 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
467 468
468 469 if data:
469 470 for uid in data.get('delete_patterns', []):
470 471 settings_model.delete_entries(uid)
471 472
472 473 for pattern in data.get('patterns', []):
473 474 for setting, value, type_ in pattern:
474 475 sett = settings_model.create_or_update_setting(
475 476 setting, value, type_)
476 477 Session().add(sett)
477 478
478 479 Session().commit()
479 480
480 481 SettingsModel().invalidate_settings_cache()
481 482 h.flash(_('Updated issue tracker entries'), category='success')
482 483 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
483 484
484 485 @LoginRequired()
485 486 @HasPermissionAllDecorator('hg.admin')
486 487 @CSRFRequired()
487 488 def settings_issuetracker_delete(self):
488 489 _ = self.request.translate
489 490 self.load_default_context()
490 491 uid = self.request.POST.get('uid')
491 492 try:
492 493 IssueTrackerSettingsModel().delete_entries(uid)
493 494 except Exception:
494 495 log.exception('Failed to delete issue tracker setting %s', uid)
495 496 raise HTTPNotFound()
496 497
497 498 SettingsModel().invalidate_settings_cache()
498 499 h.flash(_('Removed issue tracker entry.'), category='success')
499 500
500 501 return {'deleted': uid}
501 502
502 503 @LoginRequired()
503 504 @HasPermissionAllDecorator('hg.admin')
504 505 def settings_email(self):
505 506 c = self.load_default_context()
506 507 c.active = 'email'
507 508 c.rhodecode_ini = rhodecode.CONFIG
508 509
509 510 data = render('rhodecode:templates/admin/settings/settings.mako',
510 511 self._get_template_context(c), self.request)
511 512 html = formencode.htmlfill.render(
512 513 data,
513 514 defaults=self._form_defaults(),
514 515 encoding="UTF-8",
515 516 force_defaults=False
516 517 )
517 518 return Response(html)
518 519
519 520 @LoginRequired()
520 521 @HasPermissionAllDecorator('hg.admin')
521 522 @CSRFRequired()
522 523 def settings_email_update(self):
523 524 _ = self.request.translate
524 525 c = self.load_default_context()
525 526 c.active = 'email'
526 527
527 528 test_email = self.request.POST.get('test_email')
528 529
529 530 if not test_email:
530 531 h.flash(_('Please enter email address'), category='error')
531 532 raise HTTPFound(h.route_path('admin_settings_email'))
532 533
533 534 email_kwargs = {
534 535 'date': datetime.datetime.now(),
535 536 'user': self._rhodecode_db_user
536 537 }
537 538
538 539 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
539 540 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
540 541
541 542 recipients = [test_email] if test_email else None
542 543
543 544 run_task(tasks.send_email, recipients, subject,
544 545 email_body_plaintext, email_body)
545 546
546 547 h.flash(_('Send email task created'), category='success')
547 548 raise HTTPFound(h.route_path('admin_settings_email'))
548 549
549 550 @LoginRequired()
550 551 @HasPermissionAllDecorator('hg.admin')
551 552 def settings_hooks(self):
552 553 c = self.load_default_context()
553 554 c.active = 'hooks'
554 555
555 556 model = SettingsModel()
556 557 c.hooks = model.get_builtin_hooks()
557 558 c.custom_hooks = model.get_custom_hooks()
558 559
559 560 data = render('rhodecode:templates/admin/settings/settings.mako',
560 561 self._get_template_context(c), self.request)
561 562 html = formencode.htmlfill.render(
562 563 data,
563 564 defaults=self._form_defaults(),
564 565 encoding="UTF-8",
565 566 force_defaults=False
566 567 )
567 568 return Response(html)
568 569
569 570 @LoginRequired()
570 571 @HasPermissionAllDecorator('hg.admin')
571 572 @CSRFRequired()
572 573 def settings_hooks_update(self):
573 574 _ = self.request.translate
574 575 c = self.load_default_context()
575 576 c.active = 'hooks'
576 577 if c.visual.allow_custom_hooks_settings:
577 578 ui_key = self.request.POST.get('new_hook_ui_key')
578 579 ui_value = self.request.POST.get('new_hook_ui_value')
579 580
580 581 hook_id = self.request.POST.get('hook_id')
581 582 new_hook = False
582 583
583 584 model = SettingsModel()
584 585 try:
585 586 if ui_value and ui_key:
586 587 model.create_or_update_hook(ui_key, ui_value)
587 588 h.flash(_('Added new hook'), category='success')
588 589 new_hook = True
589 590 elif hook_id:
590 591 RhodeCodeUi.delete(hook_id)
591 592 Session().commit()
592 593
593 594 # check for edits
594 595 update = False
595 596 _d = self.request.POST.dict_of_lists()
596 597 for k, v in zip(_d.get('hook_ui_key', []),
597 598 _d.get('hook_ui_value_new', [])):
598 599 model.create_or_update_hook(k, v)
599 600 update = True
600 601
601 602 if update and not new_hook:
602 603 h.flash(_('Updated hooks'), category='success')
603 604 Session().commit()
604 605 except Exception:
605 606 log.exception("Exception during hook creation")
606 607 h.flash(_('Error occurred during hook creation'),
607 608 category='error')
608 609
609 610 raise HTTPFound(h.route_path('admin_settings_hooks'))
610 611
611 612 @LoginRequired()
612 613 @HasPermissionAllDecorator('hg.admin')
613 614 def settings_search(self):
614 615 c = self.load_default_context()
615 616 c.active = 'search'
616 617
617 618 c.searcher = searcher_from_config(self.request.registry.settings)
618 619 c.statistics = c.searcher.statistics(self.request.translate)
619 620
620 621 return self._get_template_context(c)
621 622
622 623 @LoginRequired()
623 624 @HasPermissionAllDecorator('hg.admin')
624 625 def settings_automation(self):
625 626 c = self.load_default_context()
626 627 c.active = 'automation'
627 628
628 629 return self._get_template_context(c)
629 630
630 631 @LoginRequired()
631 632 @HasPermissionAllDecorator('hg.admin')
632 633 def settings_labs(self):
633 634 c = self.load_default_context()
634 635 if not c.labs_active:
635 636 raise HTTPFound(h.route_path('admin_settings'))
636 637
637 638 c.active = 'labs'
638 639 c.lab_settings = _LAB_SETTINGS
639 640
640 641 data = render('rhodecode:templates/admin/settings/settings.mako',
641 642 self._get_template_context(c), self.request)
642 643 html = formencode.htmlfill.render(
643 644 data,
644 645 defaults=self._form_defaults(),
645 646 encoding="UTF-8",
646 647 force_defaults=False
647 648 )
648 649 return Response(html)
649 650
650 651 @LoginRequired()
651 652 @HasPermissionAllDecorator('hg.admin')
652 653 @CSRFRequired()
653 654 def settings_labs_update(self):
654 655 _ = self.request.translate
655 656 c = self.load_default_context()
656 657 c.active = 'labs'
657 658
658 659 application_form = LabsSettingsForm(self.request.translate)()
659 660 try:
660 661 form_result = application_form.to_python(dict(self.request.POST))
661 662 except formencode.Invalid as errors:
662 663 h.flash(
663 664 _("Some form inputs contain invalid data."),
664 665 category='error')
665 666 data = render('rhodecode:templates/admin/settings/settings.mako',
666 667 self._get_template_context(c), self.request)
667 668 html = formencode.htmlfill.render(
668 669 data,
669 670 defaults=errors.value,
670 671 errors=errors.error_dict or {},
671 672 prefix_error=False,
672 673 encoding="UTF-8",
673 674 force_defaults=False
674 675 )
675 676 return Response(html)
676 677
677 678 try:
678 679 session = Session()
679 680 for setting in _LAB_SETTINGS:
680 681 setting_name = setting.key[len('rhodecode_'):]
681 682 sett = SettingsModel().create_or_update_setting(
682 683 setting_name, form_result[setting.key], setting.type)
683 684 session.add(sett)
684 685
685 686 except Exception:
686 687 log.exception('Exception while updating lab settings')
687 688 h.flash(_('Error occurred during updating labs settings'),
688 689 category='error')
689 690 else:
690 691 Session().commit()
691 692 SettingsModel().invalidate_settings_cache()
692 693 h.flash(_('Updated Labs settings'), category='success')
693 694 raise HTTPFound(h.route_path('admin_settings_labs'))
694 695
695 696 data = render('rhodecode:templates/admin/settings/settings.mako',
696 697 self._get_template_context(c), self.request)
697 698 html = formencode.htmlfill.render(
698 699 data,
699 700 defaults=self._form_defaults(),
700 701 encoding="UTF-8",
701 702 force_defaults=False
702 703 )
703 704 return Response(html)
704 705
705 706
706 707 # :param key: name of the setting including the 'rhodecode_' prefix
707 708 # :param type: the RhodeCodeSetting type to use.
708 709 # :param group: the i18ned group in which we should dispaly this setting
709 710 # :param label: the i18ned label we should display for this setting
710 711 # :param help: the i18ned help we should dispaly for this setting
711 712 LabSetting = collections.namedtuple(
712 713 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
713 714
714 715
715 716 # This list has to be kept in sync with the form
716 717 # rhodecode.model.forms.LabsSettingsForm.
717 718 _LAB_SETTINGS = [
718 719
719 720 ]
@@ -1,293 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import (
38 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 39 from rhodecode.model.db import Statistics, CacheKey, User
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _load_commits_context(self, c):
56 56 p = safe_int(self.request.GET.get('page'), 1)
57 57 size = safe_int(self.request.GET.get('size'), 10)
58 58
59 59 def url_generator(page_num):
60 60 query_params = {
61 61 'page': page_num,
62 62 'size': size
63 63 }
64 64 return h.route_path(
65 65 'repo_summary_commits',
66 66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67 67
68 68 pre_load = ['author', 'branch', 'date', 'message']
69 69 try:
70 70 collection = self.rhodecode_vcs_repo.get_commits(
71 71 pre_load=pre_load, translate_tags=False)
72 72 except EmptyRepositoryError:
73 73 collection = self.rhodecode_vcs_repo
74 74
75 75 c.repo_commits = h.RepoPage(
76 76 collection, page=p, items_per_page=size, url_maker=url_generator)
77 77 page_ids = [x.raw_id for x in c.repo_commits]
78 78 c.comments = self.db_repo.get_comments(page_ids)
79 79 c.statuses = self.db_repo.statuses(page_ids)
80 80
81 81 def _prepare_and_set_clone_url(self, c):
82 82 username = ''
83 83 if self._rhodecode_user.username != User.DEFAULT_USER:
84 84 username = safe_str(self._rhodecode_user.username)
85 85
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
86 _def_clone_uri = c.clone_uri_tmpl
87 _def_clone_uri_id = c.clone_uri_id_tmpl
87 88 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
88 89
89 if '{repo}' in _def_clone_uri:
90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
91 elif '{repoid}' in _def_clone_uri:
92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
93
94 90 c.clone_repo_url = self.db_repo.clone_url(
95 91 user=username, uri_tmpl=_def_clone_uri)
96 92 c.clone_repo_url_id = self.db_repo.clone_url(
97 93 user=username, uri_tmpl=_def_clone_uri_id)
98 94 c.clone_repo_url_ssh = self.db_repo.clone_url(
99 95 uri_tmpl=_def_clone_uri_ssh, ssh=True)
100 96
101 97 @LoginRequired()
102 98 @HasRepoPermissionAnyDecorator(
103 99 'repository.read', 'repository.write', 'repository.admin')
104 100 def summary_commits(self):
105 101 c = self.load_default_context()
106 102 self._prepare_and_set_clone_url(c)
107 103 self._load_commits_context(c)
108 104 return self._get_template_context(c)
109 105
110 106 @LoginRequired()
111 107 @HasRepoPermissionAnyDecorator(
112 108 'repository.read', 'repository.write', 'repository.admin')
113 109 def summary(self):
114 110 c = self.load_default_context()
115 111
116 112 # Prepare the clone URL
117 113 self._prepare_and_set_clone_url(c)
118 114
119 115 # If enabled, get statistics data
120 116 c.show_stats = bool(self.db_repo.enable_statistics)
121 117
122 118 stats = Session().query(Statistics) \
123 119 .filter(Statistics.repository == self.db_repo) \
124 120 .scalar()
125 121
126 122 c.stats_percentage = 0
127 123
128 124 if stats and stats.languages:
129 125 c.no_data = False is self.db_repo.enable_statistics
130 126 lang_stats_d = json.loads(stats.languages)
131 127
132 128 # Sort first by decreasing count and second by the file extension,
133 129 # so we have a consistent output.
134 130 lang_stats_items = sorted(lang_stats_d.iteritems(),
135 131 key=lambda k: (-k[1], k[0]))[:10]
136 132 lang_stats = [(x, {"count": y,
137 133 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
138 134 for x, y in lang_stats_items]
139 135
140 136 c.trending_languages = json.dumps(lang_stats)
141 137 else:
142 138 c.no_data = True
143 139 c.trending_languages = json.dumps({})
144 140
145 141 scm_model = ScmModel()
146 142 c.enable_downloads = self.db_repo.enable_downloads
147 143 c.repository_followers = scm_model.get_followers(self.db_repo)
148 144 c.repository_forks = scm_model.get_forks(self.db_repo)
149 145
150 146 # first interaction with the VCS instance after here...
151 147 if c.repository_requirements_missing:
152 148 self.request.override_renderer = \
153 149 'rhodecode:templates/summary/missing_requirements.mako'
154 150 return self._get_template_context(c)
155 151
156 152 c.readme_data, c.readme_file = \
157 153 self._get_readme_data(self.db_repo, c.visual.default_renderer)
158 154
159 155 # loads the summary commits template context
160 156 self._load_commits_context(c)
161 157
162 158 return self._get_template_context(c)
163 159
164 160 @LoginRequired()
165 161 @HasRepoPermissionAnyDecorator(
166 162 'repository.read', 'repository.write', 'repository.admin')
167 163 def repo_stats(self):
168 164 show_stats = bool(self.db_repo.enable_statistics)
169 165 repo_id = self.db_repo.repo_id
170 166
171 167 landing_commit = self.db_repo.get_landing_commit()
172 168 if isinstance(landing_commit, EmptyCommit):
173 169 return {'size': 0, 'code_stats': {}}
174 170
175 171 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
176 172 cache_on = cache_seconds > 0
177 173
178 174 log.debug(
179 175 'Computing REPO STATS for repo_id %s commit_id `%s` '
180 176 'with caching: %s[TTL: %ss]' % (
181 177 repo_id, landing_commit, cache_on, cache_seconds or 0))
182 178
183 179 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
184 180 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
185 181
186 182 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
187 183 condition=cache_on)
188 184 def compute_stats(repo_id, commit_id, _show_stats):
189 185 code_stats = {}
190 186 size = 0
191 187 try:
192 188 commit = self.db_repo.get_commit(commit_id)
193 189
194 190 for node in commit.get_filenodes_generator():
195 191 size += node.size
196 192 if not _show_stats:
197 193 continue
198 194 ext = string.lower(node.extension)
199 195 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
200 196 if ext_info:
201 197 if ext in code_stats:
202 198 code_stats[ext]['count'] += 1
203 199 else:
204 200 code_stats[ext] = {"count": 1, "desc": ext_info}
205 201 except (EmptyRepositoryError, CommitDoesNotExistError):
206 202 pass
207 203 return {'size': h.format_byte_size_binary(size),
208 204 'code_stats': code_stats}
209 205
210 206 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
211 207 return stats
212 208
213 209 @LoginRequired()
214 210 @HasRepoPermissionAnyDecorator(
215 211 'repository.read', 'repository.write', 'repository.admin')
216 212 def repo_refs_data(self):
217 213 _ = self.request.translate
218 214 self.load_default_context()
219 215
220 216 repo = self.rhodecode_vcs_repo
221 217 refs_to_create = [
222 218 (_("Branch"), repo.branches, 'branch'),
223 219 (_("Tag"), repo.tags, 'tag'),
224 220 (_("Bookmark"), repo.bookmarks, 'book'),
225 221 ]
226 222 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
227 223 data = {
228 224 'more': False,
229 225 'results': res
230 226 }
231 227 return data
232 228
233 229 @LoginRequired()
234 230 @HasRepoPermissionAnyDecorator(
235 231 'repository.read', 'repository.write', 'repository.admin')
236 232 def repo_refs_changelog_data(self):
237 233 _ = self.request.translate
238 234 self.load_default_context()
239 235
240 236 repo = self.rhodecode_vcs_repo
241 237
242 238 refs_to_create = [
243 239 (_("Branches"), repo.branches, 'branch'),
244 240 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
245 241 # TODO: enable when vcs can handle bookmarks filters
246 242 # (_("Bookmarks"), repo.bookmarks, "book"),
247 243 ]
248 244 res = self._create_reference_data(
249 245 repo, self.db_repo_name, refs_to_create)
250 246 data = {
251 247 'more': False,
252 248 'results': res
253 249 }
254 250 return data
255 251
256 252 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
257 253 format_ref_id = get_format_ref_id(repo)
258 254
259 255 result = []
260 256 for title, refs, ref_type in refs_to_create:
261 257 if refs:
262 258 result.append({
263 259 'text': title,
264 260 'children': self._create_reference_items(
265 261 repo, full_repo_name, refs, ref_type,
266 262 format_ref_id),
267 263 })
268 264 return result
269 265
270 266 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
271 267 result = []
272 268 is_svn = h.is_svn(repo)
273 269 for ref_name, raw_id in refs.iteritems():
274 270 files_url = self._create_files_url(
275 271 repo, full_repo_name, ref_name, raw_id, is_svn)
276 272 result.append({
277 273 'text': ref_name,
278 274 'id': format_ref_id(ref_name, raw_id),
279 275 'raw_id': raw_id,
280 276 'type': ref_type,
281 277 'files_url': files_url,
282 278 'idx': 0,
283 279 })
284 280 return result
285 281
286 282 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
287 283 use_commit_id = '/' in ref_name or is_svn
288 284 return h.route_path(
289 285 'repo_files',
290 286 repo_name=full_repo_name,
291 287 f_path=ref_name if is_svn else '',
292 288 commit_id=raw_id if use_commit_id else ref_name,
293 289 _query=dict(at=ref_name))
@@ -1,626 +1,627 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 """
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, rc_realm=''):
215 215 self.realm = realm
216 216 self.rc_realm = rc_realm
217 217 self.initial_call = initial_call_detection
218 218 self.authfunc = authfunc
219 219 self.registry = registry
220 220 self.acl_repo_name = acl_repo_name
221 221 self._rc_auth_http_code = auth_http_code
222 222
223 223 def _get_response_from_code(self, http_code):
224 224 try:
225 225 return get_exception(safe_int(http_code))
226 226 except Exception:
227 227 log.exception('Failed to fetch response for code %s', http_code)
228 228 return HTTPForbidden
229 229
230 230 def get_rc_realm(self):
231 231 return safe_str(self.rc_realm)
232 232
233 233 def build_authentication(self):
234 234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 235 if self._rc_auth_http_code and not self.initial_call:
236 236 # return alternative HTTP code if alternative http return code
237 237 # is specified in RhodeCode config, but ONLY if it's not the
238 238 # FIRST call
239 239 custom_response_klass = self._get_response_from_code(
240 240 self._rc_auth_http_code)
241 241 return custom_response_klass(headers=head)
242 242 return HTTPUnauthorized(headers=head)
243 243
244 244 def authenticate(self, environ):
245 245 authorization = AUTHORIZATION(environ)
246 246 if not authorization:
247 247 return self.build_authentication()
248 248 (authmeth, auth) = authorization.split(' ', 1)
249 249 if 'basic' != authmeth.lower():
250 250 return self.build_authentication()
251 251 auth = auth.strip().decode('base64')
252 252 _parts = auth.split(':', 1)
253 253 if len(_parts) == 2:
254 254 username, password = _parts
255 255 auth_data = self.authfunc(
256 256 username, password, environ, VCS_TYPE,
257 257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 258 if auth_data:
259 259 return {'username': username, 'auth_data': auth_data}
260 260 if username and password:
261 261 # we mark that we actually executed authentication once, at
262 262 # that point we can use the alternative auth code
263 263 self.initial_call = False
264 264
265 265 return self.build_authentication()
266 266
267 267 __call__ = authenticate
268 268
269 269
270 270 def calculate_version_hash(config):
271 271 return sha1(
272 272 config.get('beaker.session.secret', '') +
273 273 rhodecode.__version__)[:8]
274 274
275 275
276 276 def get_current_lang(request):
277 277 # NOTE(marcink): remove after pyramid move
278 278 try:
279 279 return translation.get_lang()[0]
280 280 except:
281 281 pass
282 282
283 283 return getattr(request, '_LOCALE_', request.locale_name)
284 284
285 285
286 286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 287 """
288 288 Attach variables into template context called `c`.
289 289 """
290 290 config = request.registry.settings
291 291
292 292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 293 context.rc_config = rc_config
294 294 context.rhodecode_version = rhodecode.__version__
295 295 context.rhodecode_edition = config.get('rhodecode.edition')
296 296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
297 297 # unique secret + version does not leak the version but keep consistency
298 298 context.rhodecode_version_hash = calculate_version_hash(config)
299 299
300 300 # Default language set for the incoming request
301 301 context.language = get_current_lang(request)
302 302
303 303 # Visual options
304 304 context.visual = AttributeDict({})
305 305
306 306 # DB stored Visual Items
307 307 context.visual.show_public_icon = str2bool(
308 308 rc_config.get('rhodecode_show_public_icon'))
309 309 context.visual.show_private_icon = str2bool(
310 310 rc_config.get('rhodecode_show_private_icon'))
311 311 context.visual.stylify_metatags = str2bool(
312 312 rc_config.get('rhodecode_stylify_metatags'))
313 313 context.visual.dashboard_items = safe_int(
314 314 rc_config.get('rhodecode_dashboard_items', 100))
315 315 context.visual.admin_grid_items = safe_int(
316 316 rc_config.get('rhodecode_admin_grid_items', 100))
317 317 context.visual.show_revision_number = str2bool(
318 318 rc_config.get('rhodecode_show_revision_number', True))
319 319 context.visual.show_sha_length = safe_int(
320 320 rc_config.get('rhodecode_show_sha_length', 100))
321 321 context.visual.repository_fields = str2bool(
322 322 rc_config.get('rhodecode_repository_fields'))
323 323 context.visual.show_version = str2bool(
324 324 rc_config.get('rhodecode_show_version'))
325 325 context.visual.use_gravatar = str2bool(
326 326 rc_config.get('rhodecode_use_gravatar'))
327 327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
328 328 context.visual.default_renderer = rc_config.get(
329 329 'rhodecode_markup_renderer', 'rst')
330 330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
331 331 context.visual.rhodecode_support_url = \
332 332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
333 333
334 334 context.visual.affected_files_cut_off = 60
335 335
336 336 context.pre_code = rc_config.get('rhodecode_pre_code')
337 337 context.post_code = rc_config.get('rhodecode_post_code')
338 338 context.rhodecode_name = rc_config.get('rhodecode_title')
339 339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
340 340 # if we have specified default_encoding in the request, it has more
341 341 # priority
342 342 if request.GET.get('default_encoding'):
343 343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
344 344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
345 context.clone_uri_id_tmpl = rc_config.get('rhodecode_clone_uri_id_tmpl')
345 346 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
346 347
347 348 # INI stored
348 349 context.labs_active = str2bool(
349 350 config.get('labs_settings_active', 'false'))
350 351 context.ssh_enabled = str2bool(
351 352 config.get('ssh.generate_authorized_keyfile', 'false'))
352 353 context.ssh_key_generator_enabled = str2bool(
353 354 config.get('ssh.enable_ui_key_generator', 'true'))
354 355
355 356 context.visual.allow_repo_location_change = str2bool(
356 357 config.get('allow_repo_location_change', True))
357 358 context.visual.allow_custom_hooks_settings = str2bool(
358 359 config.get('allow_custom_hooks_settings', True))
359 360 context.debug_style = str2bool(config.get('debug_style', False))
360 361
361 362 context.rhodecode_instanceid = config.get('instance_id')
362 363
363 364 context.visual.cut_off_limit_diff = safe_int(
364 365 config.get('cut_off_limit_diff'))
365 366 context.visual.cut_off_limit_file = safe_int(
366 367 config.get('cut_off_limit_file'))
367 368
368 369 context.license = AttributeDict({})
369 370 context.license.hide_license_info = str2bool(
370 371 config.get('license.hide_license_info', False))
371 372
372 373 # AppEnlight
373 374 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
374 375 context.appenlight_api_public_key = config.get(
375 376 'appenlight.api_public_key', '')
376 377 context.appenlight_server_url = config.get('appenlight.server_url', '')
377 378
378 379 diffmode = {
379 380 "unified": "unified",
380 381 "sideside": "sideside"
381 382 }.get(request.GET.get('diffmode'))
382 383
383 384 if is_api is not None:
384 385 is_api = hasattr(request, 'rpc_user')
385 386 session_attrs = {
386 387 # defaults
387 388 "clone_url_format": "http",
388 389 "diffmode": "sideside",
389 390 "license_fingerprint": request.session.get('license_fingerprint')
390 391 }
391 392
392 393 if not is_api:
393 394 # don't access pyramid session for API calls
394 395 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
395 396 request.session['rc_user_session_attr.diffmode'] = diffmode
396 397
397 398 # session settings per user
398 399
399 400 for k, v in request.session.items():
400 401 pref = 'rc_user_session_attr.'
401 402 if k and k.startswith(pref):
402 403 k = k[len(pref):]
403 404 session_attrs[k] = v
404 405
405 406 context.user_session_attrs = session_attrs
406 407
407 408 # JS template context
408 409 context.template_context = {
409 410 'repo_name': None,
410 411 'repo_type': None,
411 412 'repo_landing_commit': None,
412 413 'rhodecode_user': {
413 414 'username': None,
414 415 'email': None,
415 416 'notification_status': False
416 417 },
417 418 'session_attrs': session_attrs,
418 419 'visual': {
419 420 'default_renderer': None
420 421 },
421 422 'commit_data': {
422 423 'commit_id': None
423 424 },
424 425 'pull_request_data': {'pull_request_id': None},
425 426 'timeago': {
426 427 'refresh_time': 120 * 1000,
427 428 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
428 429 },
429 430 'pyramid_dispatch': {
430 431
431 432 },
432 433 'extra': {'plugins': {}}
433 434 }
434 435 # END CONFIG VARS
435 436 if is_api:
436 437 csrf_token = None
437 438 else:
438 439 csrf_token = auth.get_csrf_token(session=request.session)
439 440
440 441 context.csrf_token = csrf_token
441 442 context.backends = rhodecode.BACKENDS.keys()
442 443
443 444 unread_count = 0
444 445 user_bookmark_list = []
445 446 if user_id:
446 447 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
447 448 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
448 449 context.unread_notifications = unread_count
449 450 context.bookmark_items = user_bookmark_list
450 451
451 452 # web case
452 453 if hasattr(request, 'user'):
453 454 context.auth_user = request.user
454 455 context.rhodecode_user = request.user
455 456
456 457 # api case
457 458 if hasattr(request, 'rpc_user'):
458 459 context.auth_user = request.rpc_user
459 460 context.rhodecode_user = request.rpc_user
460 461
461 462 # attach the whole call context to the request
462 463 request.call_context = context
463 464
464 465
465 466 def get_auth_user(request):
466 467 environ = request.environ
467 468 session = request.session
468 469
469 470 ip_addr = get_ip_addr(environ)
470 471
471 472 # make sure that we update permissions each time we call controller
472 473 _auth_token = (
473 474 # ?auth_token=XXX
474 475 request.GET.get('auth_token', '')
475 476 # ?api_key=XXX !LEGACY
476 477 or request.GET.get('api_key', '')
477 478 # or headers....
478 479 or request.headers.get('X-Rc-Auth-Token', '')
479 480 )
480 481 if not _auth_token and request.matchdict:
481 482 url_auth_token = request.matchdict.get('_auth_token')
482 483 _auth_token = url_auth_token
483 484 if _auth_token:
484 485 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
485 486
486 487 if _auth_token:
487 488 # when using API_KEY we assume user exists, and
488 489 # doesn't need auth based on cookies.
489 490 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
490 491 authenticated = False
491 492 else:
492 493 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
493 494 try:
494 495 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
495 496 ip_addr=ip_addr)
496 497 except UserCreationError as e:
497 498 h.flash(e, 'error')
498 499 # container auth or other auth functions that create users
499 500 # on the fly can throw this exception signaling that there's
500 501 # issue with user creation, explanation should be provided
501 502 # in Exception itself. We then create a simple blank
502 503 # AuthUser
503 504 auth_user = AuthUser(ip_addr=ip_addr)
504 505
505 506 # in case someone changes a password for user it triggers session
506 507 # flush and forces a re-login
507 508 if password_changed(auth_user, session):
508 509 session.invalidate()
509 510 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
510 511 auth_user = AuthUser(ip_addr=ip_addr)
511 512
512 513 authenticated = cookie_store.get('is_authenticated')
513 514
514 515 if not auth_user.is_authenticated and auth_user.is_user_object:
515 516 # user is not authenticated and not empty
516 517 auth_user.set_authenticated(authenticated)
517 518
518 519 return auth_user, _auth_token
519 520
520 521
521 522 def h_filter(s):
522 523 """
523 524 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
524 525 we wrap this with additional functionality that converts None to empty
525 526 strings
526 527 """
527 528 if s is None:
528 529 return markupsafe.Markup()
529 530 return markupsafe.escape(s)
530 531
531 532
532 533 def add_events_routes(config):
533 534 """
534 535 Adds routing that can be used in events. Because some events are triggered
535 536 outside of pyramid context, we need to bootstrap request with some
536 537 routing registered
537 538 """
538 539
539 540 from rhodecode.apps._base import ADMIN_PREFIX
540 541
541 542 config.add_route(name='home', pattern='/')
542 543 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
543 544 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
544 545
545 546 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
546 547 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
547 548 config.add_route(name='repo_summary', pattern='/{repo_name}')
548 549 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
549 550 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
550 551
551 552 config.add_route(name='pullrequest_show',
552 553 pattern='/{repo_name}/pull-request/{pull_request_id}')
553 554 config.add_route(name='pull_requests_global',
554 555 pattern='/pull-request/{pull_request_id}')
555 556
556 557 config.add_route(name='repo_commit',
557 558 pattern='/{repo_name}/changeset/{commit_id}')
558 559 config.add_route(name='repo_files',
559 560 pattern='/{repo_name}/files/{commit_id}/{f_path}')
560 561
561 562 config.add_route(name='hovercard_user',
562 563 pattern='/_hovercard/user/{user_id}')
563 564
564 565 config.add_route(name='hovercard_user_group',
565 566 pattern='/_hovercard/user_group/{user_group_id}')
566 567
567 568 config.add_route(name='hovercard_pull_request',
568 569 pattern='/_hovercard/pull_request/{pull_request_id}')
569 570
570 571 config.add_route(name='hovercard_repo_commit',
571 572 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
572 573
573 574
574 575 def bootstrap_config(request):
575 576 import pyramid.testing
576 577 registry = pyramid.testing.Registry('RcTestRegistry')
577 578
578 579 config = pyramid.testing.setUp(registry=registry, request=request)
579 580
580 581 # allow pyramid lookup in testing
581 582 config.include('pyramid_mako')
582 583 config.include('rhodecode.lib.rc_beaker')
583 584 config.include('rhodecode.lib.rc_cache')
584 585
585 586 add_events_routes(config)
586 587
587 588 return config
588 589
589 590
590 591 def bootstrap_request(**kwargs):
591 592 import pyramid.testing
592 593
593 594 class TestRequest(pyramid.testing.DummyRequest):
594 595 application_url = kwargs.pop('application_url', 'http://example.com')
595 596 host = kwargs.pop('host', 'example.com:80')
596 597 domain = kwargs.pop('domain', 'example.com')
597 598
598 599 def translate(self, msg):
599 600 return msg
600 601
601 602 def plularize(self, singular, plural, n):
602 603 return singular
603 604
604 605 def get_partial_renderer(self, tmpl_name):
605 606
606 607 from rhodecode.lib.partial_renderer import get_partial_renderer
607 608 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
608 609
609 610 _call_context = TemplateArgs()
610 611 _call_context.visual = TemplateArgs()
611 612 _call_context.visual.show_sha_length = 12
612 613 _call_context.visual.show_revision_number = True
613 614
614 615 @property
615 616 def call_context(self):
616 617 return self._call_context
617 618
618 619 class TestDummySession(pyramid.testing.DummySession):
619 620 def save(*arg, **kw):
620 621 pass
621 622
622 623 request = TestRequest(**kwargs)
623 624 request.session = TestDummySession()
624 625
625 626 return request
626 627
@@ -1,678 +1,679 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 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args=None):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.cli_args = cli_args or {}
70 70 self.init_db(SESSION=SESSION)
71 71 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 72
73 73 def db_exists(self):
74 74 if not self.sa:
75 75 self.init_db()
76 76 try:
77 77 self.sa.query(RhodeCodeUi)\
78 78 .filter(RhodeCodeUi.ui_key == '/')\
79 79 .scalar()
80 80 return True
81 81 except Exception:
82 82 return False
83 83 finally:
84 84 self.sa.rollback()
85 85
86 86 def get_ask_ok_func(self, param):
87 87 if param not in [None]:
88 88 # return a function lambda that has a default set to param
89 89 return lambda *args, **kwargs: param
90 90 else:
91 91 from rhodecode.lib.utils import ask_ok
92 92 return ask_ok
93 93
94 94 def init_db(self, SESSION=None):
95 95 if SESSION:
96 96 self.sa = SESSION
97 97 else:
98 98 # init new sessions
99 99 engine = create_engine(self.dburi, echo=self.log_sql)
100 100 init_model(engine)
101 101 self.sa = Session()
102 102
103 103 def create_tables(self, override=False):
104 104 """
105 105 Create a auth database
106 106 """
107 107
108 108 log.info("Existing database with the same name is going to be destroyed.")
109 109 log.info("Setup command will run DROP ALL command on that database.")
110 110 if self.tests:
111 111 destroy = True
112 112 else:
113 113 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
114 114 if not destroy:
115 115 log.info('Nothing done.')
116 116 sys.exit(0)
117 117 if destroy:
118 118 Base.metadata.drop_all()
119 119
120 120 checkfirst = not override
121 121 Base.metadata.create_all(checkfirst=checkfirst)
122 122 log.info('Created tables for %s', self.dbname)
123 123
124 124 def set_db_version(self):
125 125 ver = DbMigrateVersion()
126 126 ver.version = __dbversion__
127 127 ver.repository_id = 'rhodecode_db_migrations'
128 128 ver.repository_path = 'versions'
129 129 self.sa.add(ver)
130 130 log.info('db version set to: %s', __dbversion__)
131 131
132 132 def run_post_migration_tasks(self):
133 133 """
134 134 Run various tasks before actually doing migrations
135 135 """
136 136 # delete cache keys on each upgrade
137 137 total = CacheKey.query().count()
138 138 log.info("Deleting (%s) cache keys now...", total)
139 139 CacheKey.delete_all_cache()
140 140
141 141 def upgrade(self, version=None):
142 142 """
143 143 Upgrades given database schema to given revision following
144 144 all needed steps, to perform the upgrade
145 145
146 146 """
147 147
148 148 from rhodecode.lib.dbmigrate.migrate.versioning import api
149 149 from rhodecode.lib.dbmigrate.migrate.exceptions import \
150 150 DatabaseNotControlledError
151 151
152 152 if 'sqlite' in self.dburi:
153 153 print(
154 154 '********************** WARNING **********************\n'
155 155 'Make sure your version of sqlite is at least 3.7.X. \n'
156 156 'Earlier versions are known to fail on some migrations\n'
157 157 '*****************************************************\n')
158 158
159 159 upgrade = self.ask_ok(
160 160 'You are about to perform a database upgrade. Make '
161 161 'sure you have backed up your database. '
162 162 'Continue ? [y/n]')
163 163 if not upgrade:
164 164 log.info('No upgrade performed')
165 165 sys.exit(0)
166 166
167 167 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
168 168 'rhodecode/lib/dbmigrate')
169 169 db_uri = self.dburi
170 170
171 171 if version:
172 172 DbMigrateVersion.set_version(version)
173 173
174 174 try:
175 175 curr_version = api.db_version(db_uri, repository_path)
176 176 msg = ('Found current database db_uri under version '
177 177 'control with version {}'.format(curr_version))
178 178
179 179 except (RuntimeError, DatabaseNotControlledError):
180 180 curr_version = 1
181 181 msg = ('Current database is not under version control. Setting '
182 182 'as version %s' % curr_version)
183 183 api.version_control(db_uri, repository_path, curr_version)
184 184
185 185 notify(msg)
186 186
187 187
188 188 if curr_version == __dbversion__:
189 189 log.info('This database is already at the newest version')
190 190 sys.exit(0)
191 191
192 192 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
193 193 notify('attempting to upgrade database from '
194 194 'version %s to version %s' % (curr_version, __dbversion__))
195 195
196 196 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
197 197 _step = None
198 198 for step in upgrade_steps:
199 199 notify('performing upgrade step %s' % step)
200 200 time.sleep(0.5)
201 201
202 202 api.upgrade(db_uri, repository_path, step)
203 203 self.sa.rollback()
204 204 notify('schema upgrade for step %s completed' % (step,))
205 205
206 206 _step = step
207 207
208 208 self.run_post_migration_tasks()
209 209 notify('upgrade to version %s successful' % _step)
210 210
211 211 def fix_repo_paths(self):
212 212 """
213 213 Fixes an old RhodeCode version path into new one without a '*'
214 214 """
215 215
216 216 paths = self.sa.query(RhodeCodeUi)\
217 217 .filter(RhodeCodeUi.ui_key == '/')\
218 218 .scalar()
219 219
220 220 paths.ui_value = paths.ui_value.replace('*', '')
221 221
222 222 try:
223 223 self.sa.add(paths)
224 224 self.sa.commit()
225 225 except Exception:
226 226 self.sa.rollback()
227 227 raise
228 228
229 229 def fix_default_user(self):
230 230 """
231 231 Fixes an old default user with some 'nicer' default values,
232 232 used mostly for anonymous access
233 233 """
234 234 def_user = self.sa.query(User)\
235 235 .filter(User.username == User.DEFAULT_USER)\
236 236 .one()
237 237
238 238 def_user.name = 'Anonymous'
239 239 def_user.lastname = 'User'
240 240 def_user.email = User.DEFAULT_USER_EMAIL
241 241
242 242 try:
243 243 self.sa.add(def_user)
244 244 self.sa.commit()
245 245 except Exception:
246 246 self.sa.rollback()
247 247 raise
248 248
249 249 def fix_settings(self):
250 250 """
251 251 Fixes rhodecode settings and adds ga_code key for google analytics
252 252 """
253 253
254 254 hgsettings3 = RhodeCodeSetting('ga_code', '')
255 255
256 256 try:
257 257 self.sa.add(hgsettings3)
258 258 self.sa.commit()
259 259 except Exception:
260 260 self.sa.rollback()
261 261 raise
262 262
263 263 def create_admin_and_prompt(self):
264 264
265 265 # defaults
266 266 defaults = self.cli_args
267 267 username = defaults.get('username')
268 268 password = defaults.get('password')
269 269 email = defaults.get('email')
270 270
271 271 if username is None:
272 272 username = raw_input('Specify admin username:')
273 273 if password is None:
274 274 password = self._get_admin_password()
275 275 if not password:
276 276 # second try
277 277 password = self._get_admin_password()
278 278 if not password:
279 279 sys.exit()
280 280 if email is None:
281 281 email = raw_input('Specify admin email:')
282 282 api_key = self.cli_args.get('api_key')
283 283 self.create_user(username, password, email, True,
284 284 strict_creation_check=False,
285 285 api_key=api_key)
286 286
287 287 def _get_admin_password(self):
288 288 password = getpass.getpass('Specify admin password '
289 289 '(min 6 chars):')
290 290 confirm = getpass.getpass('Confirm password:')
291 291
292 292 if password != confirm:
293 293 log.error('passwords mismatch')
294 294 return False
295 295 if len(password) < 6:
296 296 log.error('password is too short - use at least 6 characters')
297 297 return False
298 298
299 299 return password
300 300
301 301 def create_test_admin_and_users(self):
302 302 log.info('creating admin and regular test users')
303 303 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
304 304 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
305 305 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
306 306 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
307 307 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
308 308
309 309 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
310 310 TEST_USER_ADMIN_EMAIL, True, api_key=True)
311 311
312 312 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
313 313 TEST_USER_REGULAR_EMAIL, False, api_key=True)
314 314
315 315 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
316 316 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
317 317
318 318 def create_ui_settings(self, repo_store_path):
319 319 """
320 320 Creates ui settings, fills out hooks
321 321 and disables dotencode
322 322 """
323 323 settings_model = SettingsModel(sa=self.sa)
324 324 from rhodecode.lib.vcs.backends.hg import largefiles_store
325 325 from rhodecode.lib.vcs.backends.git import lfs_store
326 326
327 327 # Build HOOKS
328 328 hooks = [
329 329 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
330 330
331 331 # HG
332 332 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
333 333 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
334 334 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
335 335 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
336 336 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
337 337 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
338 338
339 339 ]
340 340
341 341 for key, value in hooks:
342 342 hook_obj = settings_model.get_ui_by_key(key)
343 343 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
344 344 hooks2.ui_section = 'hooks'
345 345 hooks2.ui_key = key
346 346 hooks2.ui_value = value
347 347 self.sa.add(hooks2)
348 348
349 349 # enable largefiles
350 350 largefiles = RhodeCodeUi()
351 351 largefiles.ui_section = 'extensions'
352 352 largefiles.ui_key = 'largefiles'
353 353 largefiles.ui_value = ''
354 354 self.sa.add(largefiles)
355 355
356 356 # set default largefiles cache dir, defaults to
357 357 # /repo_store_location/.cache/largefiles
358 358 largefiles = RhodeCodeUi()
359 359 largefiles.ui_section = 'largefiles'
360 360 largefiles.ui_key = 'usercache'
361 361 largefiles.ui_value = largefiles_store(repo_store_path)
362 362
363 363 self.sa.add(largefiles)
364 364
365 365 # set default lfs cache dir, defaults to
366 366 # /repo_store_location/.cache/lfs_store
367 367 lfsstore = RhodeCodeUi()
368 368 lfsstore.ui_section = 'vcs_git_lfs'
369 369 lfsstore.ui_key = 'store_location'
370 370 lfsstore.ui_value = lfs_store(repo_store_path)
371 371
372 372 self.sa.add(lfsstore)
373 373
374 374 # enable hgsubversion disabled by default
375 375 hgsubversion = RhodeCodeUi()
376 376 hgsubversion.ui_section = 'extensions'
377 377 hgsubversion.ui_key = 'hgsubversion'
378 378 hgsubversion.ui_value = ''
379 379 hgsubversion.ui_active = False
380 380 self.sa.add(hgsubversion)
381 381
382 382 # enable hgevolve disabled by default
383 383 hgevolve = RhodeCodeUi()
384 384 hgevolve.ui_section = 'extensions'
385 385 hgevolve.ui_key = 'evolve'
386 386 hgevolve.ui_value = ''
387 387 hgevolve.ui_active = False
388 388 self.sa.add(hgevolve)
389 389
390 390 hgevolve = RhodeCodeUi()
391 391 hgevolve.ui_section = 'experimental'
392 392 hgevolve.ui_key = 'evolution'
393 393 hgevolve.ui_value = ''
394 394 hgevolve.ui_active = False
395 395 self.sa.add(hgevolve)
396 396
397 397 hgevolve = RhodeCodeUi()
398 398 hgevolve.ui_section = 'experimental'
399 399 hgevolve.ui_key = 'evolution.exchange'
400 400 hgevolve.ui_value = ''
401 401 hgevolve.ui_active = False
402 402 self.sa.add(hgevolve)
403 403
404 404 hgevolve = RhodeCodeUi()
405 405 hgevolve.ui_section = 'extensions'
406 406 hgevolve.ui_key = 'topic'
407 407 hgevolve.ui_value = ''
408 408 hgevolve.ui_active = False
409 409 self.sa.add(hgevolve)
410 410
411 411 # enable hggit disabled by default
412 412 hggit = RhodeCodeUi()
413 413 hggit.ui_section = 'extensions'
414 414 hggit.ui_key = 'hggit'
415 415 hggit.ui_value = ''
416 416 hggit.ui_active = False
417 417 self.sa.add(hggit)
418 418
419 419 # set svn branch defaults
420 420 branches = ["/branches/*", "/trunk"]
421 421 tags = ["/tags/*"]
422 422
423 423 for branch in branches:
424 424 settings_model.create_ui_section_value(
425 425 RhodeCodeUi.SVN_BRANCH_ID, branch)
426 426
427 427 for tag in tags:
428 428 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
429 429
430 430 def create_auth_plugin_options(self, skip_existing=False):
431 431 """
432 432 Create default auth plugin settings, and make it active
433 433
434 434 :param skip_existing:
435 435 """
436 436 defaults = [
437 437 ('auth_plugins',
438 438 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
439 439 'list'),
440 440
441 441 ('auth_authtoken_enabled',
442 442 'True',
443 443 'bool'),
444 444
445 445 ('auth_rhodecode_enabled',
446 446 'True',
447 447 'bool'),
448 448 ]
449 449 for k, v, t in defaults:
450 450 if (skip_existing and
451 451 SettingsModel().get_setting_by_name(k) is not None):
452 452 log.debug('Skipping option %s', k)
453 453 continue
454 454 setting = RhodeCodeSetting(k, v, t)
455 455 self.sa.add(setting)
456 456
457 457 def create_default_options(self, skip_existing=False):
458 458 """Creates default settings"""
459 459
460 460 for k, v, t in [
461 461 ('default_repo_enable_locking', False, 'bool'),
462 462 ('default_repo_enable_downloads', False, 'bool'),
463 463 ('default_repo_enable_statistics', False, 'bool'),
464 464 ('default_repo_private', False, 'bool'),
465 465 ('default_repo_type', 'hg', 'unicode')]:
466 466
467 467 if (skip_existing and
468 468 SettingsModel().get_setting_by_name(k) is not None):
469 469 log.debug('Skipping option %s', k)
470 470 continue
471 471 setting = RhodeCodeSetting(k, v, t)
472 472 self.sa.add(setting)
473 473
474 474 def fixup_groups(self):
475 475 def_usr = User.get_default_user()
476 476 for g in RepoGroup.query().all():
477 477 g.group_name = g.get_new_name(g.name)
478 478 self.sa.add(g)
479 479 # get default perm
480 480 default = UserRepoGroupToPerm.query()\
481 481 .filter(UserRepoGroupToPerm.group == g)\
482 482 .filter(UserRepoGroupToPerm.user == def_usr)\
483 483 .scalar()
484 484
485 485 if default is None:
486 486 log.debug('missing default permission for group %s adding', g)
487 487 perm_obj = RepoGroupModel()._create_default_perms(g)
488 488 self.sa.add(perm_obj)
489 489
490 490 def reset_permissions(self, username):
491 491 """
492 492 Resets permissions to default state, useful when old systems had
493 493 bad permissions, we must clean them up
494 494
495 495 :param username:
496 496 """
497 497 default_user = User.get_by_username(username)
498 498 if not default_user:
499 499 return
500 500
501 501 u2p = UserToPerm.query()\
502 502 .filter(UserToPerm.user == default_user).all()
503 503 fixed = False
504 504 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
505 505 for p in u2p:
506 506 Session().delete(p)
507 507 fixed = True
508 508 self.populate_default_permissions()
509 509 return fixed
510 510
511 511 def config_prompt(self, test_repo_path='', retries=3):
512 512 defaults = self.cli_args
513 513 _path = defaults.get('repos_location')
514 514 if retries == 3:
515 515 log.info('Setting up repositories config')
516 516
517 517 if _path is not None:
518 518 path = _path
519 519 elif not self.tests and not test_repo_path:
520 520 path = raw_input(
521 521 'Enter a valid absolute path to store repositories. '
522 522 'All repositories in that path will be added automatically:'
523 523 )
524 524 else:
525 525 path = test_repo_path
526 526 path_ok = True
527 527
528 528 # check proper dir
529 529 if not os.path.isdir(path):
530 530 path_ok = False
531 531 log.error('Given path %s is not a valid directory', path)
532 532
533 533 elif not os.path.isabs(path):
534 534 path_ok = False
535 535 log.error('Given path %s is not an absolute path', path)
536 536
537 537 # check if path is at least readable.
538 538 if not os.access(path, os.R_OK):
539 539 path_ok = False
540 540 log.error('Given path %s is not readable', path)
541 541
542 542 # check write access, warn user about non writeable paths
543 543 elif not os.access(path, os.W_OK) and path_ok:
544 544 log.warning('No write permission to given path %s', path)
545 545
546 546 q = ('Given path %s is not writeable, do you want to '
547 547 'continue with read only mode ? [y/n]' % (path,))
548 548 if not self.ask_ok(q):
549 549 log.error('Canceled by user')
550 550 sys.exit(-1)
551 551
552 552 if retries == 0:
553 553 sys.exit('max retries reached')
554 554 if not path_ok:
555 555 retries -= 1
556 556 return self.config_prompt(test_repo_path, retries)
557 557
558 558 real_path = os.path.normpath(os.path.realpath(path))
559 559
560 560 if real_path != os.path.normpath(path):
561 561 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
562 562 'given path as %s ? [y/n]') % (real_path,)
563 563 if not self.ask_ok(q):
564 564 log.error('Canceled by user')
565 565 sys.exit(-1)
566 566
567 567 return real_path
568 568
569 569 def create_settings(self, path):
570 570
571 571 self.create_ui_settings(path)
572 572
573 573 ui_config = [
574 574 ('web', 'push_ssl', 'False'),
575 575 ('web', 'allow_archive', 'gz zip bz2'),
576 576 ('web', 'allow_push', '*'),
577 577 ('web', 'baseurl', '/'),
578 578 ('paths', '/', path),
579 579 ('phases', 'publish', 'True')
580 580 ]
581 581 for section, key, value in ui_config:
582 582 ui_conf = RhodeCodeUi()
583 583 setattr(ui_conf, 'ui_section', section)
584 584 setattr(ui_conf, 'ui_key', key)
585 585 setattr(ui_conf, 'ui_value', value)
586 586 self.sa.add(ui_conf)
587 587
588 588 # rhodecode app settings
589 589 settings = [
590 590 ('realm', 'RhodeCode', 'unicode'),
591 591 ('title', '', 'unicode'),
592 592 ('pre_code', '', 'unicode'),
593 593 ('post_code', '', 'unicode'),
594 594
595 595 # Visual
596 596 ('show_public_icon', True, 'bool'),
597 597 ('show_private_icon', True, 'bool'),
598 598 ('stylify_metatags', False, 'bool'),
599 599 ('dashboard_items', 100, 'int'),
600 600 ('admin_grid_items', 25, 'int'),
601 601
602 602 ('markup_renderer', 'markdown', 'unicode'),
603 603
604 604 ('show_version', True, 'bool'),
605 605 ('show_revision_number', True, 'bool'),
606 606 ('show_sha_length', 12, 'int'),
607 607
608 608 ('use_gravatar', False, 'bool'),
609 609 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
610 610
611 611 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
612 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
612 613 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
613 614 ('support_url', '', 'unicode'),
614 615 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
615 616
616 617 # VCS Settings
617 618 ('pr_merge_enabled', True, 'bool'),
618 619 ('use_outdated_comments', True, 'bool'),
619 620 ('diff_cache', True, 'bool'),
620 621 ]
621 622
622 623 for key, val, type_ in settings:
623 624 sett = RhodeCodeSetting(key, val, type_)
624 625 self.sa.add(sett)
625 626
626 627 self.create_auth_plugin_options()
627 628 self.create_default_options()
628 629
629 630 log.info('created ui config')
630 631
631 632 def create_user(self, username, password, email='', admin=False,
632 633 strict_creation_check=True, api_key=None):
633 634 log.info('creating user `%s`', username)
634 635 user = UserModel().create_or_update(
635 636 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
636 637 active=True, admin=admin, extern_type="rhodecode",
637 638 strict_creation_check=strict_creation_check)
638 639
639 640 if api_key:
640 641 log.info('setting a new default auth token for user `%s`', username)
641 642 UserModel().add_auth_token(
642 643 user=user, lifetime_minutes=-1,
643 644 role=UserModel.auth_token_role.ROLE_ALL,
644 645 description=u'BUILTIN TOKEN')
645 646
646 647 def create_default_user(self):
647 648 log.info('creating default user')
648 649 # create default user for handling default permissions.
649 650 user = UserModel().create_or_update(username=User.DEFAULT_USER,
650 651 password=str(uuid.uuid1())[:20],
651 652 email=User.DEFAULT_USER_EMAIL,
652 653 firstname=u'Anonymous',
653 654 lastname=u'User',
654 655 strict_creation_check=False)
655 656 # based on configuration options activate/de-activate this user which
656 657 # controlls anonymous access
657 658 if self.cli_args.get('public_access') is False:
658 659 log.info('Public access disabled')
659 660 user.active = False
660 661 Session().add(user)
661 662 Session().commit()
662 663
663 664 def create_permissions(self):
664 665 """
665 666 Creates all permissions defined in the system
666 667 """
667 668 # module.(access|create|change|delete)_[name]
668 669 # module.(none|read|write|admin)
669 670 log.info('creating permissions')
670 671 PermissionModel(self.sa).create_permissions()
671 672
672 673 def populate_default_permissions(self):
673 674 """
674 675 Populate default permissions. It will create only the default
675 676 permissions that are missing, and not alter already defined ones
676 677 """
677 678 log.info('creating default user permissions')
678 679 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,640 +1,641 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 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import deform
45 45 import logging
46 46 import formencode
47 47
48 48 from pkg_resources import resource_filename
49 49 from formencode import All, Pipe
50 50
51 51 from pyramid.threadlocal import get_current_request
52 52
53 53 from rhodecode import BACKENDS
54 54 from rhodecode.lib import helpers
55 55 from rhodecode.model import validators as v
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 deform_templates = resource_filename('deform', 'templates')
61 61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 62 search_path = (rhodecode_templates, deform_templates)
63 63
64 64
65 65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 67 def __call__(self, template_name, **kw):
68 68 kw['h'] = helpers
69 69 kw['request'] = get_current_request()
70 70 return self.load(template_name)(**kw)
71 71
72 72
73 73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 74 deform.Form.set_default_renderer(form_renderer)
75 75
76 76
77 77 def LoginForm(localizer):
78 78 _ = localizer
79 79
80 80 class _LoginForm(formencode.Schema):
81 81 allow_extra_fields = True
82 82 filter_extra_fields = True
83 83 username = v.UnicodeString(
84 84 strip=True,
85 85 min=1,
86 86 not_empty=True,
87 87 messages={
88 88 'empty': _(u'Please enter a login'),
89 89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 90 }
91 91 )
92 92
93 93 password = v.UnicodeString(
94 94 strip=False,
95 95 min=3,
96 96 max=72,
97 97 not_empty=True,
98 98 messages={
99 99 'empty': _(u'Please enter a password'),
100 100 'tooShort': _(u'Enter %(min)i characters or more')}
101 101 )
102 102
103 103 remember = v.StringBoolean(if_missing=False)
104 104
105 105 chained_validators = [v.ValidAuth(localizer)]
106 106 return _LoginForm
107 107
108 108
109 109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 110 old_data = old_data or {}
111 111 available_languages = available_languages or []
112 112 _ = localizer
113 113
114 114 class _UserForm(formencode.Schema):
115 115 allow_extra_fields = True
116 116 filter_extra_fields = True
117 117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 118 v.ValidUsername(localizer, edit, old_data))
119 119 if edit:
120 120 new_password = All(
121 121 v.ValidPassword(localizer),
122 122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 123 )
124 124 password_confirmation = All(
125 125 v.ValidPassword(localizer),
126 126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 127 )
128 128 admin = v.StringBoolean(if_missing=False)
129 129 else:
130 130 password = All(
131 131 v.ValidPassword(localizer),
132 132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 133 )
134 134 password_confirmation = All(
135 135 v.ValidPassword(localizer),
136 136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 137 )
138 138
139 139 password_change = v.StringBoolean(if_missing=False)
140 140 create_repo_group = v.StringBoolean(if_missing=False)
141 141
142 142 active = v.StringBoolean(if_missing=False)
143 143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 146 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
147 147 if_missing='')
148 148 extern_name = v.UnicodeString(strip=True)
149 149 extern_type = v.UnicodeString(strip=True)
150 150 language = v.OneOf(available_languages, hideList=False,
151 151 testValueList=True, if_missing=None)
152 152 chained_validators = [v.ValidPasswordsMatch(localizer)]
153 153 return _UserForm
154 154
155 155
156 156 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
157 157 old_data = old_data or {}
158 158 _ = localizer
159 159
160 160 class _UserGroupForm(formencode.Schema):
161 161 allow_extra_fields = True
162 162 filter_extra_fields = True
163 163
164 164 users_group_name = All(
165 165 v.UnicodeString(strip=True, min=1, not_empty=True),
166 166 v.ValidUserGroup(localizer, edit, old_data)
167 167 )
168 168 user_group_description = v.UnicodeString(strip=True, min=1,
169 169 not_empty=False)
170 170
171 171 users_group_active = v.StringBoolean(if_missing=False)
172 172
173 173 if edit:
174 174 # this is user group owner
175 175 user = All(
176 176 v.UnicodeString(not_empty=True),
177 177 v.ValidRepoUser(localizer, allow_disabled))
178 178 return _UserGroupForm
179 179
180 180
181 181 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
182 182 can_create_in_root=False, allow_disabled=False):
183 183 _ = localizer
184 184 old_data = old_data or {}
185 185 available_groups = available_groups or []
186 186
187 187 class _RepoGroupForm(formencode.Schema):
188 188 allow_extra_fields = True
189 189 filter_extra_fields = False
190 190
191 191 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
192 192 v.SlugifyName(localizer),)
193 193 group_description = v.UnicodeString(strip=True, min=1,
194 194 not_empty=False)
195 195 group_copy_permissions = v.StringBoolean(if_missing=False)
196 196
197 197 group_parent_id = v.OneOf(available_groups, hideList=False,
198 198 testValueList=True, not_empty=True)
199 199 enable_locking = v.StringBoolean(if_missing=False)
200 200 chained_validators = [
201 201 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
202 202
203 203 if edit:
204 204 # this is repo group owner
205 205 user = All(
206 206 v.UnicodeString(not_empty=True),
207 207 v.ValidRepoUser(localizer, allow_disabled))
208 208 return _RepoGroupForm
209 209
210 210
211 211 def RegisterForm(localizer, edit=False, old_data=None):
212 212 _ = localizer
213 213 old_data = old_data or {}
214 214
215 215 class _RegisterForm(formencode.Schema):
216 216 allow_extra_fields = True
217 217 filter_extra_fields = True
218 218 username = All(
219 219 v.ValidUsername(localizer, edit, old_data),
220 220 v.UnicodeString(strip=True, min=1, not_empty=True)
221 221 )
222 222 password = All(
223 223 v.ValidPassword(localizer),
224 224 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
225 225 )
226 226 password_confirmation = All(
227 227 v.ValidPassword(localizer),
228 228 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
229 229 )
230 230 active = v.StringBoolean(if_missing=False)
231 231 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
232 232 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
233 233 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
234 234
235 235 chained_validators = [v.ValidPasswordsMatch(localizer)]
236 236 return _RegisterForm
237 237
238 238
239 239 def PasswordResetForm(localizer):
240 240 _ = localizer
241 241
242 242 class _PasswordResetForm(formencode.Schema):
243 243 allow_extra_fields = True
244 244 filter_extra_fields = True
245 245 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
246 246 return _PasswordResetForm
247 247
248 248
249 249 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
250 250 _ = localizer
251 251 old_data = old_data or {}
252 252 repo_groups = repo_groups or []
253 253 supported_backends = BACKENDS.keys()
254 254
255 255 class _RepoForm(formencode.Schema):
256 256 allow_extra_fields = True
257 257 filter_extra_fields = False
258 258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 260 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 261 v.OneOf(repo_groups, hideList=True))
262 262 repo_type = v.OneOf(supported_backends, required=False,
263 263 if_missing=old_data.get('repo_type'))
264 264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 265 repo_private = v.StringBoolean(if_missing=False)
266 266 repo_copy_permissions = v.StringBoolean(if_missing=False)
267 267 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
268 268
269 269 repo_enable_statistics = v.StringBoolean(if_missing=False)
270 270 repo_enable_downloads = v.StringBoolean(if_missing=False)
271 271 repo_enable_locking = v.StringBoolean(if_missing=False)
272 272
273 273 if edit:
274 274 # this is repo owner
275 275 user = All(
276 276 v.UnicodeString(not_empty=True),
277 277 v.ValidRepoUser(localizer, allow_disabled))
278 278 clone_uri_change = v.UnicodeString(
279 279 not_empty=False, if_missing=v.Missing)
280 280
281 281 chained_validators = [v.ValidCloneUri(localizer),
282 282 v.ValidRepoName(localizer, edit, old_data)]
283 283 return _RepoForm
284 284
285 285
286 286 def RepoPermsForm(localizer):
287 287 _ = localizer
288 288
289 289 class _RepoPermsForm(formencode.Schema):
290 290 allow_extra_fields = True
291 291 filter_extra_fields = False
292 292 chained_validators = [v.ValidPerms(localizer, type_='repo')]
293 293 return _RepoPermsForm
294 294
295 295
296 296 def RepoGroupPermsForm(localizer, valid_recursive_choices):
297 297 _ = localizer
298 298
299 299 class _RepoGroupPermsForm(formencode.Schema):
300 300 allow_extra_fields = True
301 301 filter_extra_fields = False
302 302 recursive = v.OneOf(valid_recursive_choices)
303 303 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
304 304 return _RepoGroupPermsForm
305 305
306 306
307 307 def UserGroupPermsForm(localizer):
308 308 _ = localizer
309 309
310 310 class _UserPermsForm(formencode.Schema):
311 311 allow_extra_fields = True
312 312 filter_extra_fields = False
313 313 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
314 314 return _UserPermsForm
315 315
316 316
317 317 def RepoFieldForm(localizer):
318 318 _ = localizer
319 319
320 320 class _RepoFieldForm(formencode.Schema):
321 321 filter_extra_fields = True
322 322 allow_extra_fields = True
323 323
324 324 new_field_key = All(v.FieldKey(localizer),
325 325 v.UnicodeString(strip=True, min=3, not_empty=True))
326 326 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
327 327 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
328 328 if_missing='str')
329 329 new_field_label = v.UnicodeString(not_empty=False)
330 330 new_field_desc = v.UnicodeString(not_empty=False)
331 331 return _RepoFieldForm
332 332
333 333
334 334 def RepoForkForm(localizer, edit=False, old_data=None,
335 335 supported_backends=BACKENDS.keys(), repo_groups=None):
336 336 _ = localizer
337 337 old_data = old_data or {}
338 338 repo_groups = repo_groups or []
339 339
340 340 class _RepoForkForm(formencode.Schema):
341 341 allow_extra_fields = True
342 342 filter_extra_fields = False
343 343 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
344 344 v.SlugifyName(localizer))
345 345 repo_group = All(v.CanWriteGroup(localizer, ),
346 346 v.OneOf(repo_groups, hideList=True))
347 347 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
348 348 description = v.UnicodeString(strip=True, min=1, not_empty=True)
349 349 private = v.StringBoolean(if_missing=False)
350 350 copy_permissions = v.StringBoolean(if_missing=False)
351 351 fork_parent_id = v.UnicodeString()
352 352 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
353 353 return _RepoForkForm
354 354
355 355
356 356 def ApplicationSettingsForm(localizer):
357 357 _ = localizer
358 358
359 359 class _ApplicationSettingsForm(formencode.Schema):
360 360 allow_extra_fields = True
361 361 filter_extra_fields = False
362 362 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
363 363 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
364 364 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
365 365 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
366 366 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
367 367 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
368 368 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
369 369 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
370 370 return _ApplicationSettingsForm
371 371
372 372
373 373 def ApplicationVisualisationForm(localizer):
374 374 from rhodecode.model.db import Repository
375 375 _ = localizer
376 376
377 377 class _ApplicationVisualisationForm(formencode.Schema):
378 378 allow_extra_fields = True
379 379 filter_extra_fields = False
380 380 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
381 381 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
382 382 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
383 383
384 384 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
385 385 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
386 386 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
387 387 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
388 388 rhodecode_show_version = v.StringBoolean(if_missing=False)
389 389 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
390 390 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
391 391 rhodecode_gravatar_url = v.UnicodeString(min=3)
392 392 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
393 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
393 394 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
394 395 rhodecode_support_url = v.UnicodeString()
395 396 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
396 397 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
397 398 return _ApplicationVisualisationForm
398 399
399 400
400 401 class _BaseVcsSettingsForm(formencode.Schema):
401 402
402 403 allow_extra_fields = True
403 404 filter_extra_fields = False
404 405 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
405 406 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
406 407 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
407 408
408 409 # PR/Code-review
409 410 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
410 411 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
411 412
412 413 # hg
413 414 extensions_largefiles = v.StringBoolean(if_missing=False)
414 415 extensions_evolve = v.StringBoolean(if_missing=False)
415 416 phases_publish = v.StringBoolean(if_missing=False)
416 417
417 418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
418 419 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
419 420
420 421 # git
421 422 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
422 423 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
423 424 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
424 425
425 426 # svn
426 427 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
427 428 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
428 429
429 430 # cache
430 431 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
431 432
432 433
433 434 def ApplicationUiSettingsForm(localizer):
434 435 _ = localizer
435 436
436 437 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
437 438 web_push_ssl = v.StringBoolean(if_missing=False)
438 439 paths_root_path = All(
439 440 v.ValidPath(localizer),
440 441 v.UnicodeString(strip=True, min=1, not_empty=True)
441 442 )
442 443 largefiles_usercache = All(
443 444 v.ValidPath(localizer),
444 445 v.UnicodeString(strip=True, min=2, not_empty=True))
445 446 vcs_git_lfs_store_location = All(
446 447 v.ValidPath(localizer),
447 448 v.UnicodeString(strip=True, min=2, not_empty=True))
448 449 extensions_hgsubversion = v.StringBoolean(if_missing=False)
449 450 extensions_hggit = v.StringBoolean(if_missing=False)
450 451 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
451 452 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
452 453 return _ApplicationUiSettingsForm
453 454
454 455
455 456 def RepoVcsSettingsForm(localizer, repo_name):
456 457 _ = localizer
457 458
458 459 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
459 460 inherit_global_settings = v.StringBoolean(if_missing=False)
460 461 new_svn_branch = v.ValidSvnPattern(localizer,
461 462 section='vcs_svn_branch', repo_name=repo_name)
462 463 new_svn_tag = v.ValidSvnPattern(localizer,
463 464 section='vcs_svn_tag', repo_name=repo_name)
464 465 return _RepoVcsSettingsForm
465 466
466 467
467 468 def LabsSettingsForm(localizer):
468 469 _ = localizer
469 470
470 471 class _LabSettingsForm(formencode.Schema):
471 472 allow_extra_fields = True
472 473 filter_extra_fields = False
473 474 return _LabSettingsForm
474 475
475 476
476 477 def ApplicationPermissionsForm(
477 478 localizer, register_choices, password_reset_choices,
478 479 extern_activate_choices):
479 480 _ = localizer
480 481
481 482 class _DefaultPermissionsForm(formencode.Schema):
482 483 allow_extra_fields = True
483 484 filter_extra_fields = True
484 485
485 486 anonymous = v.StringBoolean(if_missing=False)
486 487 default_register = v.OneOf(register_choices)
487 488 default_register_message = v.UnicodeString()
488 489 default_password_reset = v.OneOf(password_reset_choices)
489 490 default_extern_activate = v.OneOf(extern_activate_choices)
490 491 return _DefaultPermissionsForm
491 492
492 493
493 494 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
494 495 user_group_perms_choices):
495 496 _ = localizer
496 497
497 498 class _ObjectPermissionsForm(formencode.Schema):
498 499 allow_extra_fields = True
499 500 filter_extra_fields = True
500 501 overwrite_default_repo = v.StringBoolean(if_missing=False)
501 502 overwrite_default_group = v.StringBoolean(if_missing=False)
502 503 overwrite_default_user_group = v.StringBoolean(if_missing=False)
503 504
504 505 default_repo_perm = v.OneOf(repo_perms_choices)
505 506 default_group_perm = v.OneOf(group_perms_choices)
506 507 default_user_group_perm = v.OneOf(user_group_perms_choices)
507 508
508 509 return _ObjectPermissionsForm
509 510
510 511
511 512 def BranchPermissionsForm(localizer, branch_perms_choices):
512 513 _ = localizer
513 514
514 515 class _BranchPermissionsForm(formencode.Schema):
515 516 allow_extra_fields = True
516 517 filter_extra_fields = True
517 518 overwrite_default_branch = v.StringBoolean(if_missing=False)
518 519 default_branch_perm = v.OneOf(branch_perms_choices)
519 520
520 521 return _BranchPermissionsForm
521 522
522 523
523 524 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
524 525 repo_group_create_choices, user_group_create_choices,
525 526 fork_choices, inherit_default_permissions_choices):
526 527 _ = localizer
527 528
528 529 class _DefaultPermissionsForm(formencode.Schema):
529 530 allow_extra_fields = True
530 531 filter_extra_fields = True
531 532
532 533 anonymous = v.StringBoolean(if_missing=False)
533 534
534 535 default_repo_create = v.OneOf(create_choices)
535 536 default_repo_create_on_write = v.OneOf(create_on_write_choices)
536 537 default_user_group_create = v.OneOf(user_group_create_choices)
537 538 default_repo_group_create = v.OneOf(repo_group_create_choices)
538 539 default_fork_create = v.OneOf(fork_choices)
539 540 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
540 541 return _DefaultPermissionsForm
541 542
542 543
543 544 def UserIndividualPermissionsForm(localizer):
544 545 _ = localizer
545 546
546 547 class _DefaultPermissionsForm(formencode.Schema):
547 548 allow_extra_fields = True
548 549 filter_extra_fields = True
549 550
550 551 inherit_default_permissions = v.StringBoolean(if_missing=False)
551 552 return _DefaultPermissionsForm
552 553
553 554
554 555 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
555 556 _ = localizer
556 557 old_data = old_data or {}
557 558
558 559 class _DefaultsForm(formencode.Schema):
559 560 allow_extra_fields = True
560 561 filter_extra_fields = True
561 562 default_repo_type = v.OneOf(supported_backends)
562 563 default_repo_private = v.StringBoolean(if_missing=False)
563 564 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
564 565 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
565 566 default_repo_enable_locking = v.StringBoolean(if_missing=False)
566 567 return _DefaultsForm
567 568
568 569
569 570 def AuthSettingsForm(localizer):
570 571 _ = localizer
571 572
572 573 class _AuthSettingsForm(formencode.Schema):
573 574 allow_extra_fields = True
574 575 filter_extra_fields = True
575 576 auth_plugins = All(v.ValidAuthPlugins(localizer),
576 577 v.UniqueListFromString(localizer)(not_empty=True))
577 578 return _AuthSettingsForm
578 579
579 580
580 581 def UserExtraEmailForm(localizer):
581 582 _ = localizer
582 583
583 584 class _UserExtraEmailForm(formencode.Schema):
584 585 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
585 586 return _UserExtraEmailForm
586 587
587 588
588 589 def UserExtraIpForm(localizer):
589 590 _ = localizer
590 591
591 592 class _UserExtraIpForm(formencode.Schema):
592 593 ip = v.ValidIp(localizer)(not_empty=True)
593 594 return _UserExtraIpForm
594 595
595 596
596 597 def PullRequestForm(localizer, repo_id):
597 598 _ = localizer
598 599
599 600 class ReviewerForm(formencode.Schema):
600 601 user_id = v.Int(not_empty=True)
601 602 reasons = All()
602 603 rules = All(v.UniqueList(localizer, convert=int)())
603 604 mandatory = v.StringBoolean()
604 605 role = v.String(if_missing='reviewer')
605 606
606 607 class ObserverForm(formencode.Schema):
607 608 user_id = v.Int(not_empty=True)
608 609 reasons = All()
609 610 rules = All(v.UniqueList(localizer, convert=int)())
610 611 mandatory = v.StringBoolean()
611 612 role = v.String(if_missing='observer')
612 613
613 614 class _PullRequestForm(formencode.Schema):
614 615 allow_extra_fields = True
615 616 filter_extra_fields = True
616 617
617 618 common_ancestor = v.UnicodeString(strip=True, required=True)
618 619 source_repo = v.UnicodeString(strip=True, required=True)
619 620 source_ref = v.UnicodeString(strip=True, required=True)
620 621 target_repo = v.UnicodeString(strip=True, required=True)
621 622 target_ref = v.UnicodeString(strip=True, required=True)
622 623 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
623 624 v.UniqueList(localizer)(not_empty=True))
624 625 review_members = formencode.ForEach(ReviewerForm())
625 626 observer_members = formencode.ForEach(ObserverForm())
626 627 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
627 628 pullrequest_desc = v.UnicodeString(strip=True, required=False)
628 629 description_renderer = v.UnicodeString(strip=True, required=False)
629 630
630 631 return _PullRequestForm
631 632
632 633
633 634 def IssueTrackerPatternsForm(localizer):
634 635 _ = localizer
635 636
636 637 class _IssueTrackerPatternsForm(formencode.Schema):
637 638 allow_extra_fields = True
638 639 filter_extra_fields = False
639 640 chained_validators = [v.ValidPattern(localizer)]
640 641 return _IssueTrackerPatternsForm
@@ -1,230 +1,233 b''
1 1 ${h.secure_form(h.route_path('admin_settings_visual_update'), request=request)}
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading" id="general">
5 5 <h3 class="panel-title">${_('General')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="checkbox">
9 9 ${h.checkbox('rhodecode_repository_fields','True')}
10 10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
11 11 </div>
12 12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
13 13
14 14 <div></div>
15 15 <div class="checkbox">
16 16 ${h.checkbox('rhodecode_show_version','True')}
17 17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
18 18 </div>
19 19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
20 20 </div>
21 21 </div>
22 22
23 23
24 24 <div class="panel panel-default">
25 25 <div class="panel-heading" id="gravatars">
26 26 <h3 class="panel-title">${_('Gravatars')}</h3>
27 27 </div>
28 28 <div class="panel-body">
29 29 <div class="checkbox">
30 30 ${h.checkbox('rhodecode_use_gravatar','True')}
31 31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
32 32 </div>
33 33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
34 34
35 35 <div class="label">
36 36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
37 37 </div>
38 38 <div class="input">
39 39 <div class="field">
40 40 ${h.text('rhodecode_gravatar_url', size='100%')}
41 41 </div>
42 42
43 43 <div class="field">
44 44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
45 45 Following variables of the URL will be replaced accordingly.
46 46 {scheme} 'http' or 'https' sent from running RhodeCode server,
47 47 {email} user email,
48 48 {md5email} md5 hash of the user email (like at gravatar.com),
49 49 {size} size of the image that is expected from the server application,
50 50 {netloc} network location/server host of running RhodeCode server''')}</span>
51 51 </div>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56
57 57 <div class="panel panel-default">
58 58 <div class="panel-heading" id="meta-tagging">
59 59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
60 60 </div>
61 61 <div class="panel-body">
62 62 <div class="checkbox">
63 63 ${h.checkbox('rhodecode_stylify_metatags','True')}
64 64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
65 65 </div>
66 66 <span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
67 67 <div>
68 68 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
69 69 ${dt.metatags_help()}
70 70 </div>
71 71 </div>
72 72 </div>
73 73
74 74
75 75 <div class="panel panel-default">
76 76 <div class="panel-heading">
77 77 <h3 class="panel-title">${_('Dashboard Items')}</h3>
78 78 </div>
79 79 <div class="panel-body">
80 80 <div class="label">
81 81 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
82 82 </div>
83 83 <div class="field input">
84 84 ${h.text('rhodecode_dashboard_items',size=5)}
85 85 </div>
86 86 <div class="field">
87 87 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
88 88 </div>
89 89
90 90 <div class="label">
91 91 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
92 92 </div>
93 93 <div class="field input">
94 94 ${h.text('rhodecode_admin_grid_items',size=5)}
95 95 </div>
96 96 <div class="field">
97 97 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
98 98 </div>
99 99 </div>
100 100 </div>
101 101
102 102
103 103
104 104 <div class="panel panel-default">
105 105 <div class="panel-heading" id="commit-id">
106 106 <h3 class="panel-title">${_('Commit ID Style')}</h3>
107 107 </div>
108 108 <div class="panel-body">
109 109 <div class="label">
110 110 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
111 111 </div>
112 112 <div class="input">
113 113 <div class="field">
114 114 ${h.text('rhodecode_show_sha_length',size=5)}
115 115 </div>
116 116 <div class="field">
117 117 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
118 118 By default it's shown as r123:9043a6a4c226 this value defines the
119 119 length of the sha after the `r123:` part.''')}</span>
120 120 </div>
121 121 </div>
122 122
123 123 <div class="checkbox">
124 124 ${h.checkbox('rhodecode_show_revision_number','True')}
125 125 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
126 126 </div>
127 127 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
128 128 By default it's shown as r123:9043a6a4c226 this value defines the
129 129 if the `r123:` part is shown.''')}</span>
130 130 </div>
131 131 </div>
132 132
133 133
134 134 <div class="panel panel-default">
135 135 <div class="panel-heading" id="icons">
136 136 <h3 class="panel-title">${_('Icons')}</h3>
137 137 </div>
138 138 <div class="panel-body">
139 139 <div class="checkbox">
140 140 ${h.checkbox('rhodecode_show_public_icon','True')}
141 141 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
142 142 </div>
143 143 <div></div>
144 144
145 145 <div class="checkbox">
146 146 ${h.checkbox('rhodecode_show_private_icon','True')}
147 147 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
148 148 </div>
149 149 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
150 150 </div>
151 151 </div>
152 152
153 153
154 154 <div class="panel panel-default">
155 155 <div class="panel-heading">
156 156 <h3 class="panel-title">${_('Markup Renderer')}</h3>
157 157 </div>
158 158 <div class="panel-body">
159 159 <div class="field select">
160 160 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
161 161 </div>
162 162 <div class="field">
163 163 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
164 164 </div>
165 165 </div>
166 166 </div>
167 167
168 168 <div class="panel panel-default">
169 169 <div class="panel-heading">
170 170 <h3 class="panel-title">${_('Clone URL templates')}</h3>
171 171 </div>
172 172 <div class="panel-body">
173 173 <div class="field">
174 174 ${h.text('rhodecode_clone_uri_tmpl', size=60)} HTTP[S]
175 175 </div>
176 176 <div class="field">
177 ${h.text('rhodecode_clone_uri_id_tmpl', size=60)} HTTP UID
178 </div>
179 <div class="field">
177 180 ${h.text('rhodecode_clone_uri_ssh_tmpl', size=60)} SSH
178 181 </div>
179 182 <div class="field">
180 183 <span class="help-block">
181 184 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
182 185 {scheme} 'http' or 'https' sent from running RhodeCode server,
183 186 {user} current user username,
184 187 {sys_user} current system user running this process, Useful for ssh,
185 188 {hostname} hostname of this server running RhodeCode,
186 189 {netloc} network location/server host of running RhodeCode server,
187 190 {repo} full repository name,
188 191 {repoid} ID of repository, can be used to contruct clone-by-id''')}
189 192 </span>
190 193 </div>
191 194 </div>
192 195 </div>
193 196
194 197 <div class="panel panel-default">
195 198 <div class="panel-heading">
196 199 <h3 class="panel-title">${_('Custom Support Link')}</h3>
197 200 </div>
198 201 <div class="panel-body">
199 202 <div class="field">
200 203 ${h.text('rhodecode_support_url', size=60)}
201 204 </div>
202 205 <div class="field">
203 206 <span class="help-block">
204 207 ${_('''Custom url for the support link located at the bottom.
205 208 The default is set to %(default_url)s. In case there's a need
206 209 to change the support link to internal issue tracker, it should be done here.
207 210 ''') % {'default_url': h.route_url('rhodecode_support')}}
208 211 </span>
209 212 </div>
210 213 </div>
211 214 </div>
212 215
213 216 <div class="buttons">
214 217 ${h.submit('save',_('Save settings'),class_="btn")}
215 218 ${h.reset('reset',_('Reset'),class_="btn")}
216 219 </div>
217 220
218 221
219 222 ${h.end_form()}
220 223
221 224 <script>
222 225 $(document).ready(function() {
223 226 $('#rhodecode_markup_renderer').select2({
224 227 containerCssClass: 'drop-menu',
225 228 dropdownCssClass: 'drop-menu-dropdown',
226 229 dropdownAutoWidth: true,
227 230 minimumResultsForSearch: -1
228 231 });
229 232 });
230 233 </script>
General Comments 0
You need to be logged in to leave comments. Login now