##// END OF EJS Templates
prompts: fixed input() calls for python3
super-admin -
r5148:5ce7d4ae default
parent child Browse files
Show More
@@ -1,687 +1,687 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 """
20 20 Database creation, and setup module for RhodeCode Enterprise. Used for creation
21 21 of database as well as for migration operations
22 22 """
23 23
24 24 import os
25 25 import sys
26 26 import time
27 27 import uuid
28 28 import logging
29 29 import getpass
30 30 from os.path import dirname as dn, join as jn
31 31
32 32 from sqlalchemy.engine import create_engine
33 33
34 34 from rhodecode import __dbversion__
35 35 from rhodecode.model import init_model
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.model.db import (
38 38 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
39 39 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
40 40 from rhodecode.model.meta import Session, Base
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.repo_group import RepoGroupModel
44 44 from rhodecode.model.settings import SettingsModel
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 def notify(msg):
51 51 """
52 52 Notification for migrations messages
53 53 """
54 54 ml = len(msg) + (4 * 2)
55 55 print((('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()))
56 56
57 57
58 58 class DbManage(object):
59 59
60 60 def __init__(self, log_sql, dbconf, root, tests=False,
61 61 SESSION=None, cli_args=None, enc_key=b''):
62 62
63 63 self.dbname = dbconf.split('/')[-1]
64 64 self.tests = tests
65 65 self.root = root
66 66 self.dburi = dbconf
67 67 self.log_sql = log_sql
68 68 self.cli_args = cli_args or {}
69 69 self.sa = None
70 70 self.engine = None
71 71 self.enc_key = enc_key
72 72 # sets .sa .engine
73 73 self.init_db(SESSION=SESSION)
74 74
75 75 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
76 76
77 77 def db_exists(self):
78 78 if not self.sa:
79 79 self.init_db()
80 80 try:
81 81 self.sa.query(RhodeCodeUi)\
82 82 .filter(RhodeCodeUi.ui_key == '/')\
83 83 .scalar()
84 84 return True
85 85 except Exception:
86 86 return False
87 87 finally:
88 88 self.sa.rollback()
89 89
90 90 def get_ask_ok_func(self, param):
91 91 if param not in [None]:
92 92 # return a function lambda that has a default set to param
93 93 return lambda *args, **kwargs: param
94 94 else:
95 95 from rhodecode.lib.utils import ask_ok
96 96 return ask_ok
97 97
98 98 def init_db(self, SESSION=None):
99 99
100 100 if SESSION:
101 101 self.sa = SESSION
102 102 self.engine = SESSION.bind
103 103 else:
104 104 # init new sessions
105 105 engine = create_engine(self.dburi, echo=self.log_sql)
106 106 init_model(engine, encryption_key=self.enc_key)
107 107 self.sa = Session()
108 108 self.engine = engine
109 109
110 110 def create_tables(self, override=False):
111 111 """
112 112 Create a auth database
113 113 """
114 114
115 115 log.info("Existing database with the same name is going to be destroyed.")
116 116 log.info("Setup command will run DROP ALL command on that database.")
117 117 engine = self.engine
118 118
119 119 if self.tests:
120 120 destroy = True
121 121 else:
122 122 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
123 123 if not destroy:
124 124 log.info('db tables bootstrap: Nothing done.')
125 125 sys.exit(0)
126 126 if destroy:
127 127 Base.metadata.drop_all(bind=engine)
128 128
129 129 checkfirst = not override
130 130 Base.metadata.create_all(bind=engine, checkfirst=checkfirst)
131 131 log.info('Created tables for %s', self.dbname)
132 132
133 133 def set_db_version(self):
134 134 ver = DbMigrateVersion()
135 135 ver.version = __dbversion__
136 136 ver.repository_id = 'rhodecode_db_migrations'
137 137 ver.repository_path = 'versions'
138 138 self.sa.add(ver)
139 139 log.info('db version set to: %s', __dbversion__)
140 140
141 141 def run_post_migration_tasks(self):
142 142 """
143 143 Run various tasks before actually doing migrations
144 144 """
145 145 # delete cache keys on each upgrade
146 146 total = CacheKey.query().count()
147 147 log.info("Deleting (%s) cache keys now...", total)
148 148 CacheKey.delete_all_cache()
149 149
150 150 def upgrade(self, version=None):
151 151 """
152 152 Upgrades given database schema to given revision following
153 153 all needed steps, to perform the upgrade
154 154
155 155 """
156 156
157 157 from rhodecode.lib.dbmigrate.migrate.versioning import api
158 158 from rhodecode.lib.dbmigrate.migrate.exceptions import DatabaseNotControlledError
159 159
160 160 if 'sqlite' in self.dburi:
161 161 print(
162 162 '********************** WARNING **********************\n'
163 163 'Make sure your version of sqlite is at least 3.7.X. \n'
164 164 'Earlier versions are known to fail on some migrations\n'
165 165 '*****************************************************\n')
166 166
167 167 upgrade = self.ask_ok(
168 168 'You are about to perform a database upgrade. Make '
169 169 'sure you have backed up your database. '
170 170 'Continue ? [y/n]')
171 171 if not upgrade:
172 172 log.info('No upgrade performed')
173 173 sys.exit(0)
174 174
175 175 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
176 176 'rhodecode/lib/dbmigrate')
177 177 db_uri = self.dburi
178 178
179 179 if version:
180 180 DbMigrateVersion.set_version(version)
181 181
182 182 try:
183 183 curr_version = api.db_version(db_uri, repository_path)
184 184 msg = (f'Found current database db_uri under version '
185 185 f'control with version {curr_version}')
186 186
187 187 except (RuntimeError, DatabaseNotControlledError):
188 188 curr_version = 1
189 189 msg = f'Current database is not under version control. ' \
190 190 f'Setting as version {curr_version}'
191 191 api.version_control(db_uri, repository_path, curr_version)
192 192
193 193 notify(msg)
194 194
195 195 if curr_version == __dbversion__:
196 196 log.info('This database is already at the newest version')
197 197 sys.exit(0)
198 198
199 199 upgrade_steps = list(range(curr_version + 1, __dbversion__ + 1))
200 200 notify(f'attempting to upgrade database from '
201 201 f'version {curr_version} to version {__dbversion__}')
202 202
203 203 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
204 204 _step = None
205 205 for step in upgrade_steps:
206 206 notify(f'performing upgrade step {step}')
207 207 time.sleep(0.5)
208 208
209 209 api.upgrade(db_uri, repository_path, step)
210 210 self.sa.rollback()
211 211 notify(f'schema upgrade for step {step} completed')
212 212
213 213 _step = step
214 214
215 215 self.run_post_migration_tasks()
216 216 notify(f'upgrade to version {step} successful')
217 217
218 218 def fix_repo_paths(self):
219 219 """
220 220 Fixes an old RhodeCode version path into new one without a '*'
221 221 """
222 222
223 223 paths = self.sa.query(RhodeCodeUi)\
224 224 .filter(RhodeCodeUi.ui_key == '/')\
225 225 .scalar()
226 226
227 227 paths.ui_value = paths.ui_value.replace('*', '')
228 228
229 229 try:
230 230 self.sa.add(paths)
231 231 self.sa.commit()
232 232 except Exception:
233 233 self.sa.rollback()
234 234 raise
235 235
236 236 def fix_default_user(self):
237 237 """
238 238 Fixes an old default user with some 'nicer' default values,
239 239 used mostly for anonymous access
240 240 """
241 241 def_user = self.sa.query(User)\
242 242 .filter(User.username == User.DEFAULT_USER)\
243 243 .one()
244 244
245 245 def_user.name = 'Anonymous'
246 246 def_user.lastname = 'User'
247 247 def_user.email = User.DEFAULT_USER_EMAIL
248 248
249 249 try:
250 250 self.sa.add(def_user)
251 251 self.sa.commit()
252 252 except Exception:
253 253 self.sa.rollback()
254 254 raise
255 255
256 256 def fix_settings(self):
257 257 """
258 258 Fixes rhodecode settings and adds ga_code key for google analytics
259 259 """
260 260
261 261 hgsettings3 = RhodeCodeSetting('ga_code', '')
262 262
263 263 try:
264 264 self.sa.add(hgsettings3)
265 265 self.sa.commit()
266 266 except Exception:
267 267 self.sa.rollback()
268 268 raise
269 269
270 270 def create_admin_and_prompt(self):
271 271
272 272 # defaults
273 273 defaults = self.cli_args
274 274 username = defaults.get('username')
275 275 password = defaults.get('password')
276 276 email = defaults.get('email')
277 277
278 278 if username is None:
279 username = eval(input('Specify admin username:'))
279 username = input('Specify admin username:')
280 280 if password is None:
281 281 password = self._get_admin_password()
282 282 if not password:
283 283 # second try
284 284 password = self._get_admin_password()
285 285 if not password:
286 286 sys.exit()
287 287 if email is None:
288 email = eval(input('Specify admin email:'))
288 email = input('Specify admin email:')
289 289 api_key = self.cli_args.get('api_key')
290 290 self.create_user(username, password, email, True,
291 291 strict_creation_check=False,
292 292 api_key=api_key)
293 293
294 294 def _get_admin_password(self):
295 295 password = getpass.getpass('Specify admin password '
296 296 '(min 6 chars):')
297 297 confirm = getpass.getpass('Confirm password:')
298 298
299 299 if password != confirm:
300 300 log.error('passwords mismatch')
301 301 return False
302 302 if len(password) < 6:
303 303 log.error('password is too short - use at least 6 characters')
304 304 return False
305 305
306 306 return password
307 307
308 308 def create_test_admin_and_users(self):
309 309 log.info('creating admin and regular test users')
310 310 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
311 311 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
312 312 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
313 313 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
314 314 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
315 315
316 316 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
317 317 TEST_USER_ADMIN_EMAIL, True, api_key=True)
318 318
319 319 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
320 320 TEST_USER_REGULAR_EMAIL, False, api_key=True)
321 321
322 322 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
323 323 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
324 324
325 325 def create_ui_settings(self, repo_store_path):
326 326 """
327 327 Creates ui settings, fills out hooks
328 328 and disables dotencode
329 329 """
330 330 settings_model = SettingsModel(sa=self.sa)
331 331 from rhodecode.lib.vcs.backends.hg import largefiles_store
332 332 from rhodecode.lib.vcs.backends.git import lfs_store
333 333
334 334 # Build HOOKS
335 335 hooks = [
336 336 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
337 337
338 338 # HG
339 339 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
340 340 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
341 341 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
342 342 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
343 343 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
344 344 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
345 345
346 346 ]
347 347
348 348 for key, value in hooks:
349 349 hook_obj = settings_model.get_ui_by_key(key)
350 350 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
351 351 hooks2.ui_section = 'hooks'
352 352 hooks2.ui_key = key
353 353 hooks2.ui_value = value
354 354 self.sa.add(hooks2)
355 355
356 356 # enable largefiles
357 357 largefiles = RhodeCodeUi()
358 358 largefiles.ui_section = 'extensions'
359 359 largefiles.ui_key = 'largefiles'
360 360 largefiles.ui_value = ''
361 361 self.sa.add(largefiles)
362 362
363 363 # set default largefiles cache dir, defaults to
364 364 # /repo_store_location/.cache/largefiles
365 365 largefiles = RhodeCodeUi()
366 366 largefiles.ui_section = 'largefiles'
367 367 largefiles.ui_key = 'usercache'
368 368 largefiles.ui_value = largefiles_store(repo_store_path)
369 369
370 370 self.sa.add(largefiles)
371 371
372 372 # set default lfs cache dir, defaults to
373 373 # /repo_store_location/.cache/lfs_store
374 374 lfsstore = RhodeCodeUi()
375 375 lfsstore.ui_section = 'vcs_git_lfs'
376 376 lfsstore.ui_key = 'store_location'
377 377 lfsstore.ui_value = lfs_store(repo_store_path)
378 378
379 379 self.sa.add(lfsstore)
380 380
381 381 # enable hgsubversion disabled by default
382 382 hgsubversion = RhodeCodeUi()
383 383 hgsubversion.ui_section = 'extensions'
384 384 hgsubversion.ui_key = 'hgsubversion'
385 385 hgsubversion.ui_value = ''
386 386 hgsubversion.ui_active = False
387 387 self.sa.add(hgsubversion)
388 388
389 389 # enable hgevolve disabled by default
390 390 hgevolve = RhodeCodeUi()
391 391 hgevolve.ui_section = 'extensions'
392 392 hgevolve.ui_key = 'evolve'
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'
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 = 'experimental'
406 406 hgevolve.ui_key = 'evolution.exchange'
407 407 hgevolve.ui_value = ''
408 408 hgevolve.ui_active = False
409 409 self.sa.add(hgevolve)
410 410
411 411 hgevolve = RhodeCodeUi()
412 412 hgevolve.ui_section = 'extensions'
413 413 hgevolve.ui_key = 'topic'
414 414 hgevolve.ui_value = ''
415 415 hgevolve.ui_active = False
416 416 self.sa.add(hgevolve)
417 417
418 418 # enable hggit disabled by default
419 419 hggit = RhodeCodeUi()
420 420 hggit.ui_section = 'extensions'
421 421 hggit.ui_key = 'hggit'
422 422 hggit.ui_value = ''
423 423 hggit.ui_active = False
424 424 self.sa.add(hggit)
425 425
426 426 # set svn branch defaults
427 427 branches = ["/branches/*", "/trunk"]
428 428 tags = ["/tags/*"]
429 429
430 430 for branch in branches:
431 431 settings_model.create_ui_section_value(
432 432 RhodeCodeUi.SVN_BRANCH_ID, branch)
433 433
434 434 for tag in tags:
435 435 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
436 436
437 437 def create_auth_plugin_options(self, skip_existing=False):
438 438 """
439 439 Create default auth plugin settings, and make it active
440 440
441 441 :param skip_existing:
442 442 """
443 443 defaults = [
444 444 ('auth_plugins',
445 445 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
446 446 'list'),
447 447
448 448 ('auth_authtoken_enabled',
449 449 'True',
450 450 'bool'),
451 451
452 452 ('auth_rhodecode_enabled',
453 453 'True',
454 454 'bool'),
455 455 ]
456 456 for k, v, t in defaults:
457 457 if (skip_existing and
458 458 SettingsModel().get_setting_by_name(k) is not None):
459 459 log.debug('Skipping option %s', k)
460 460 continue
461 461 setting = RhodeCodeSetting(k, v, t)
462 462 self.sa.add(setting)
463 463
464 464 def create_default_options(self, skip_existing=False):
465 465 """Creates default settings"""
466 466
467 467 for k, v, t in [
468 468 ('default_repo_enable_locking', False, 'bool'),
469 469 ('default_repo_enable_downloads', False, 'bool'),
470 470 ('default_repo_enable_statistics', False, 'bool'),
471 471 ('default_repo_private', False, 'bool'),
472 472 ('default_repo_type', 'hg', 'unicode')]:
473 473
474 474 if (skip_existing and
475 475 SettingsModel().get_setting_by_name(k) is not None):
476 476 log.debug('Skipping option %s', k)
477 477 continue
478 478 setting = RhodeCodeSetting(k, v, t)
479 479 self.sa.add(setting)
480 480
481 481 def fixup_groups(self):
482 482 def_usr = User.get_default_user()
483 483 for g in RepoGroup.query().all():
484 484 g.group_name = g.get_new_name(g.name)
485 485 self.sa.add(g)
486 486 # get default perm
487 487 default = UserRepoGroupToPerm.query()\
488 488 .filter(UserRepoGroupToPerm.group == g)\
489 489 .filter(UserRepoGroupToPerm.user == def_usr)\
490 490 .scalar()
491 491
492 492 if default is None:
493 493 log.debug('missing default permission for group %s adding', g)
494 494 perm_obj = RepoGroupModel()._create_default_perms(g)
495 495 self.sa.add(perm_obj)
496 496
497 497 def reset_permissions(self, username):
498 498 """
499 499 Resets permissions to default state, useful when old systems had
500 500 bad permissions, we must clean them up
501 501
502 502 :param username:
503 503 """
504 504 default_user = User.get_by_username(username)
505 505 if not default_user:
506 506 return
507 507
508 508 u2p = UserToPerm.query()\
509 509 .filter(UserToPerm.user == default_user).all()
510 510 fixed = False
511 511 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
512 512 for p in u2p:
513 513 Session().delete(p)
514 514 fixed = True
515 515 self.populate_default_permissions()
516 516 return fixed
517 517
518 518 def config_prompt(self, test_repo_path='', retries=3):
519 519 defaults = self.cli_args
520 520 _path = defaults.get('repos_location')
521 521 if retries == 3:
522 522 log.info('Setting up repositories config')
523 523
524 524 if _path is not None:
525 525 path = _path
526 526 elif not self.tests and not test_repo_path:
527 path = eval(input(
527 path = input(
528 528 'Enter a valid absolute path to store repositories. '
529 529 'All repositories in that path will be added automatically:'
530 ))
530 )
531 531 else:
532 532 path = test_repo_path
533 533 path_ok = True
534 534
535 535 # check proper dir
536 536 if not os.path.isdir(path):
537 537 path_ok = False
538 538 log.error('Given path %s is not a valid directory', path)
539 539
540 540 elif not os.path.isabs(path):
541 541 path_ok = False
542 542 log.error('Given path %s is not an absolute path', path)
543 543
544 544 # check if path is at least readable.
545 545 if not os.access(path, os.R_OK):
546 546 path_ok = False
547 547 log.error('Given path %s is not readable', path)
548 548
549 549 # check write access, warn user about non writeable paths
550 550 elif not os.access(path, os.W_OK) and path_ok:
551 551 log.warning('No write permission to given path %s', path)
552 552
553 553 q = (f'Given path {path} is not writeable, do you want to '
554 554 f'continue with read only mode ? [y/n]')
555 555 if not self.ask_ok(q):
556 556 log.error('Canceled by user')
557 557 sys.exit(-1)
558 558
559 559 if retries == 0:
560 560 sys.exit('max retries reached')
561 561 if not path_ok:
562 562 retries -= 1
563 563 return self.config_prompt(test_repo_path, retries)
564 564
565 565 real_path = os.path.normpath(os.path.realpath(path))
566 566
567 567 if real_path != os.path.normpath(path):
568 568 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
569 569 f'given path as {real_path} ? [y/n]')
570 570 if not self.ask_ok(q):
571 571 log.error('Canceled by user')
572 572 sys.exit(-1)
573 573
574 574 return real_path
575 575
576 576 def create_settings(self, path):
577 577
578 578 self.create_ui_settings(path)
579 579
580 580 ui_config = [
581 581 ('web', 'push_ssl', 'False'),
582 582 ('web', 'allow_archive', 'gz zip bz2'),
583 583 ('web', 'allow_push', '*'),
584 584 ('web', 'baseurl', '/'),
585 585 ('paths', '/', path),
586 586 ('phases', 'publish', 'True')
587 587 ]
588 588 for section, key, value in ui_config:
589 589 ui_conf = RhodeCodeUi()
590 590 setattr(ui_conf, 'ui_section', section)
591 591 setattr(ui_conf, 'ui_key', key)
592 592 setattr(ui_conf, 'ui_value', value)
593 593 self.sa.add(ui_conf)
594 594
595 595 # rhodecode app settings
596 596 settings = [
597 597 ('realm', 'RhodeCode', 'unicode'),
598 598 ('title', '', 'unicode'),
599 599 ('pre_code', '', 'unicode'),
600 600 ('post_code', '', 'unicode'),
601 601
602 602 # Visual
603 603 ('show_public_icon', True, 'bool'),
604 604 ('show_private_icon', True, 'bool'),
605 605 ('stylify_metatags', True, 'bool'),
606 606 ('dashboard_items', 100, 'int'),
607 607 ('admin_grid_items', 25, 'int'),
608 608
609 609 ('markup_renderer', 'markdown', 'unicode'),
610 610
611 611 ('repository_fields', True, 'bool'),
612 612 ('show_version', True, 'bool'),
613 613 ('show_revision_number', True, 'bool'),
614 614 ('show_sha_length', 12, 'int'),
615 615
616 616 ('use_gravatar', False, 'bool'),
617 617 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
618 618
619 619 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
620 620 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
621 621 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
622 622 ('support_url', '', 'unicode'),
623 623 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
624 624
625 625 # VCS Settings
626 626 ('pr_merge_enabled', True, 'bool'),
627 627 ('use_outdated_comments', True, 'bool'),
628 628 ('diff_cache', True, 'bool'),
629 629 ]
630 630
631 631 for key, val, type_ in settings:
632 632 sett = RhodeCodeSetting(key, val, type_)
633 633 self.sa.add(sett)
634 634
635 635 self.create_auth_plugin_options()
636 636 self.create_default_options()
637 637
638 638 log.info('created ui config')
639 639
640 640 def create_user(self, username, password, email='', admin=False,
641 641 strict_creation_check=True, api_key=None):
642 642 log.info('creating user `%s`', username)
643 643 user = UserModel().create_or_update(
644 644 username, password, email, firstname='RhodeCode', lastname='Admin',
645 645 active=True, admin=admin, extern_type="rhodecode",
646 646 strict_creation_check=strict_creation_check)
647 647
648 648 if api_key:
649 649 log.info('setting a new default auth token for user `%s`', username)
650 650 UserModel().add_auth_token(
651 651 user=user, lifetime_minutes=-1,
652 652 role=UserModel.auth_token_role.ROLE_ALL,
653 653 description='BUILTIN TOKEN')
654 654
655 655 def create_default_user(self):
656 656 log.info('creating default user')
657 657 # create default user for handling default permissions.
658 658 user = UserModel().create_or_update(username=User.DEFAULT_USER,
659 659 password=str(uuid.uuid1())[:20],
660 660 email=User.DEFAULT_USER_EMAIL,
661 661 firstname='Anonymous',
662 662 lastname='User',
663 663 strict_creation_check=False)
664 664 # based on configuration options activate/de-activate this user which
665 665 # controls anonymous access
666 666 if self.cli_args.get('public_access') is False:
667 667 log.info('Public access disabled')
668 668 user.active = False
669 669 Session().add(user)
670 670 Session().commit()
671 671
672 672 def create_permissions(self):
673 673 """
674 674 Creates all permissions defined in the system
675 675 """
676 676 # module.(access|create|change|delete)_[name]
677 677 # module.(none|read|write|admin)
678 678 log.info('creating permissions')
679 679 PermissionModel(self.sa).create_permissions()
680 680
681 681 def populate_default_permissions(self):
682 682 """
683 683 Populate default permissions. It will create only the default
684 684 permissions that are missing, and not alter already defined ones
685 685 """
686 686 log.info('creating default user permissions')
687 687 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,172 +1,171 b''
1 1 # Copyright (c) 2010 Agendaless Consulting and Contributors.
2 2 # (http://www.agendaless.com), All Rights Reserved
3 3 # License: BSD-derived (http://www.repoze.org/LICENSE.txt)
4 4 # With Patches from RhodeCode GmBH
5 5
6 6 import os
7 7
8 8 from beaker import cache
9 9 from beaker.session import SessionObject, Session
10 10 from beaker.util import coerce_cache_params
11 11 from beaker.util import coerce_session_params
12 12
13 13 from pyramid.interfaces import ISession
14 14 from pyramid.settings import asbool
15 15 from zope.interface import implementer
16 16
17 17 from binascii import hexlify
18 18
19 19
20
21 20 class CustomSession(Session):
22 21 pass
23 22
24 23
25 24 def BeakerSessionFactoryConfig(**options):
26 25 """ Return a Pyramid session factory using Beaker session settings
27 26 supplied directly as ``**options``"""
28 27
29 28 class PyramidBeakerSessionObject(SessionObject):
30 29 _options = options
31 30 _cookie_on_exception = _options.pop('cookie_on_exception', True)
32 31 _constant_csrf_token = _options.pop('constant_csrf_token', False)
33 32 _sa_opts = _options.pop('sa_opts', {})
34 33
35 34 def __init__(self, request):
36 35 self._options['session_class'] = CustomSession
37 36 self._options['sa_opts'] = self._sa_opts
38 37 SessionObject.__init__(self, request.environ, **self._options)
39 38
40 39 def session_callback(_request, _response):
41 40 exception = getattr(_request, 'exception', None)
42 41 file_response = getattr(_request, '_file_response', None)
43 42 api_call = getattr(_request, 'rpc_method', None)
44 43
45 44 if file_response is not None:
46 45 return
47 46 if api_call is not None:
48 47 return
49 48
50 49 if exception is not None and not self._cookie_on_exception:
51 50 return
52 51
53 52 if self.accessed():
54 53 self.persist()
55 54 headers = self.__dict__['_headers']
56 55 if headers.get('set_cookie') and headers.get('cookie_out'):
57 56 _response.headerlist.append(('Set-Cookie', headers['cookie_out']))
58 57
59 58 request.add_response_callback(session_callback)
60 59
61 60 # ISession API
62 61
63 62 @property
64 63 def id(self):
65 64 # this is as inspected in SessionObject.__init__
66 65 if self.__dict__['_params'].get('type') != 'cookie':
67 66 return self._session().id
68 67 return None
69 68
70 69 @property
71 70 def new(self):
72 71 return self.last_accessed is None
73 72
74 73 changed = SessionObject.save
75 74
76 75 # modifying dictionary methods
77 76
78 77 @call_save
79 78 def clear(self):
80 79 return self._session().clear()
81 80
82 81 @call_save
83 82 def update(self, d, **kw):
84 83 return self._session().update(d, **kw)
85 84
86 85 @call_save
87 86 def setdefault(self, k, d=None):
88 87 return self._session().setdefault(k, d)
89 88
90 89 @call_save
91 90 def pop(self, k, d=None):
92 91 return self._session().pop(k, d)
93 92
94 93 @call_save
95 94 def popitem(self):
96 95 return self._session().popitem()
97 96
98 97 __setitem__ = call_save(SessionObject.__setitem__)
99 98 __delitem__ = call_save(SessionObject.__delitem__)
100 99
101 100 # Flash API methods
102 101 def flash(self, msg, queue='', allow_duplicate=True):
103 102 storage = self.setdefault(f'_f_{queue}', [])
104 103 if allow_duplicate or (msg not in storage):
105 104 storage.append(msg)
106 105
107 106 def pop_flash(self, queue=''):
108 107 storage = self.pop(f'_f_{queue}', [])
109 108 return storage
110 109
111 110 def peek_flash(self, queue=''):
112 storage = self.get('_f_' + queue, [])
111 storage = self.get(f'_f_{queue}', [])
113 112 return storage
114 113
115 114 # CSRF API methods
116 115 def new_csrf_token(self):
117 116 token = (self._constant_csrf_token
118 117 or hexlify(os.urandom(20)).decode('ascii'))
119 118 self['_csrft_'] = token
120 119 return token
121 120
122 121 def get_csrf_token(self):
123 122 token = self.get('_csrft_', None)
124 123 if token is None:
125 124 token = self.new_csrf_token()
126 125 return token
127 126
128 127 return implementer(ISession)(PyramidBeakerSessionObject)
129 128
130 129
131 130 def call_save(wrapped):
132 131 """ By default, in non-auto-mode beaker badly wants people to
133 132 call save even though it should know something has changed when
134 133 a mutating method is called. This hack should be removed if
135 134 Beaker ever starts to do this by default. """
136 135 def save(session, *arg, **kw):
137 136 value = wrapped(session, *arg, **kw)
138 137 session.save()
139 138 return value
140 139 save.__doc__ = wrapped.__doc__
141 140 return save
142 141
143 142
144 143 def session_factory_from_settings(settings):
145 144 """ Return a Pyramid session factory using Beaker session settings
146 145 supplied from a Paste configuration file"""
147 146 prefixes = ('session.', 'beaker.session.')
148 147 options = {}
149 148
150 149 # custom gather of our specific sqlalchemy session db configuration we need to translate this into a single entry
151 150 # dict because this is how beaker expects that.
152 151 sa_opts = {}
153 152
154 153 # Pull out any config args meant for beaker session. if there are any
155 154 for k, v in settings.items():
156 155 for prefix in prefixes:
157 156 if k.startswith(prefix):
158 157 option_name = k[len(prefix):]
159 158 if option_name == 'cookie_on_exception':
160 159 v = asbool(v)
161 160 if option_name.startswith('sa.'):
162 161 sa_opts[option_name] = v
163 162 options[option_name] = v
164 163
165 164 options = coerce_session_params(options)
166 165 options['sa_opts'] = sa_opts
167 166 return BeakerSessionFactoryConfig(**options)
168 167
169 168
170 169 def includeme(config):
171 170 session_factory = session_factory_from_settings(config.registry.settings)
172 171 config.set_session_factory(session_factory)
@@ -1,807 +1,807 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 """
20 20 Utilities library for RhodeCode
21 21 """
22 22
23 23 import datetime
24 24 import decorator
25 25 import logging
26 26 import os
27 27 import re
28 28 import sys
29 29 import shutil
30 30 import socket
31 31 import tempfile
32 32 import traceback
33 33 import tarfile
34 34 import warnings
35 35 from os.path import join as jn
36 36
37 37 import paste
38 38 import pkg_resources
39 39 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
40 40
41 41 from mako import exceptions
42 42
43 43 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
44 44 from rhodecode.lib.str_utils import safe_bytes, safe_str
45 45 from rhodecode.lib.vcs.backends.base import Config
46 46 from rhodecode.lib.vcs.exceptions import VCSError
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
48 48 from rhodecode.lib.ext_json import sjson as json
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.db import (
51 51 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
52 52 from rhodecode.model.meta import Session
53 53
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
58 58
59 59 # String which contains characters that are not allowed in slug names for
60 60 # repositories or repository groups. It is properly escaped to use it in
61 61 # regular expressions.
62 62 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
63 63
64 64 # Regex that matches forbidden characters in repo/group slugs.
65 65 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
66 66
67 67 # Regex that matches allowed characters in repo/group slugs.
68 68 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
69 69
70 70 # Regex that matches whole repo/group slugs.
71 71 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
72 72
73 73 _license_cache = None
74 74
75 75
76 76 def repo_name_slug(value):
77 77 """
78 78 Return slug of name of repository
79 79 This function is called on each creation/modification
80 80 of repository to prevent bad names in repo
81 81 """
82 82
83 83 replacement_char = '-'
84 84
85 85 slug = strip_tags(value)
86 86 slug = convert_accented_entities(slug)
87 87 slug = convert_misc_entities(slug)
88 88
89 89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 90 slug = re.sub(r'[\s]+', '-', slug)
91 91 slug = collapse(slug, replacement_char)
92 92
93 93 return slug
94 94
95 95
96 96 #==============================================================================
97 97 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
98 98 #==============================================================================
99 99 def get_repo_slug(request):
100 100 _repo = ''
101 101
102 102 if hasattr(request, 'db_repo_name'):
103 103 # if our requests has set db reference use it for name, this
104 104 # translates the example.com/_<id> into proper repo names
105 105 _repo = request.db_repo_name
106 106 elif getattr(request, 'matchdict', None):
107 107 # pyramid
108 108 _repo = request.matchdict.get('repo_name')
109 109
110 110 if _repo:
111 111 _repo = _repo.rstrip('/')
112 112 return _repo
113 113
114 114
115 115 def get_repo_group_slug(request):
116 116 _group = ''
117 117 if hasattr(request, 'db_repo_group'):
118 118 # if our requests has set db reference use it for name, this
119 119 # translates the example.com/_<id> into proper repo group names
120 120 _group = request.db_repo_group.group_name
121 121 elif getattr(request, 'matchdict', None):
122 122 # pyramid
123 123 _group = request.matchdict.get('repo_group_name')
124 124
125 125 if _group:
126 126 _group = _group.rstrip('/')
127 127 return _group
128 128
129 129
130 130 def get_user_group_slug(request):
131 131 _user_group = ''
132 132
133 133 if hasattr(request, 'db_user_group'):
134 134 _user_group = request.db_user_group.users_group_name
135 135 elif getattr(request, 'matchdict', None):
136 136 # pyramid
137 137 _user_group = request.matchdict.get('user_group_id')
138 138 _user_group_name = request.matchdict.get('user_group_name')
139 139 try:
140 140 if _user_group:
141 141 _user_group = UserGroup.get(_user_group)
142 142 elif _user_group_name:
143 143 _user_group = UserGroup.get_by_group_name(_user_group_name)
144 144
145 145 if _user_group:
146 146 _user_group = _user_group.users_group_name
147 147 except Exception:
148 148 log.exception('Failed to get user group by id and name')
149 149 # catch all failures here
150 150 return None
151 151
152 152 return _user_group
153 153
154 154
155 155 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
156 156 """
157 157 Scans given path for repos and return (name,(type,path)) tuple
158 158
159 159 :param path: path to scan for repositories
160 160 :param recursive: recursive search and return names with subdirs in front
161 161 """
162 162
163 163 # remove ending slash for better results
164 164 path = path.rstrip(os.sep)
165 165 log.debug('now scanning in %s location recursive:%s...', path, recursive)
166 166
167 167 def _get_repos(p):
168 168 dirpaths = get_dirpaths(p)
169 169 if not _is_dir_writable(p):
170 170 log.warning('repo path without write access: %s', p)
171 171
172 172 for dirpath in dirpaths:
173 173 if os.path.isfile(os.path.join(p, dirpath)):
174 174 continue
175 175 cur_path = os.path.join(p, dirpath)
176 176
177 177 # skip removed repos
178 178 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
179 179 continue
180 180
181 181 #skip .<somethin> dirs
182 182 if dirpath.startswith('.'):
183 183 continue
184 184
185 185 try:
186 186 scm_info = get_scm(cur_path)
187 187 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
188 188 except VCSError:
189 189 if not recursive:
190 190 continue
191 191 #check if this dir containts other repos for recursive scan
192 192 rec_path = os.path.join(p, dirpath)
193 193 if os.path.isdir(rec_path):
194 194 yield from _get_repos(rec_path)
195 195
196 196 return _get_repos(path)
197 197
198 198
199 199 def get_dirpaths(p: str) -> list:
200 200 try:
201 201 # OS-independable way of checking if we have at least read-only
202 202 # access or not.
203 203 dirpaths = os.listdir(p)
204 204 except OSError:
205 205 log.warning('ignoring repo path without read access: %s', p)
206 206 return []
207 207
208 208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
209 209 # decode paths and suddenly returns unicode objects itself. The items it
210 210 # cannot decode are returned as strings and cause issues.
211 211 #
212 212 # Those paths are ignored here until a solid solution for path handling has
213 213 # been built.
214 214 expected_type = type(p)
215 215
216 216 def _has_correct_type(item):
217 217 if type(item) is not expected_type:
218 218 log.error(
219 219 "Ignoring path %s since it cannot be decoded into str.",
220 220 # Using "repr" to make sure that we see the byte value in case
221 221 # of support.
222 222 repr(item))
223 223 return False
224 224 return True
225 225
226 226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
227 227
228 228 return dirpaths
229 229
230 230
231 231 def _is_dir_writable(path):
232 232 """
233 233 Probe if `path` is writable.
234 234
235 235 Due to trouble on Cygwin / Windows, this is actually probing if it is
236 236 possible to create a file inside of `path`, stat does not produce reliable
237 237 results in this case.
238 238 """
239 239 try:
240 240 with tempfile.TemporaryFile(dir=path):
241 241 pass
242 242 except OSError:
243 243 return False
244 244 return True
245 245
246 246
247 247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
248 248 """
249 249 Returns True if given path is a valid repository False otherwise.
250 250 If expect_scm param is given also, compare if given scm is the same
251 251 as expected from scm parameter. If explicit_scm is given don't try to
252 252 detect the scm, just use the given one to check if repo is valid
253 253
254 254 :param repo_name:
255 255 :param base_path:
256 256 :param expect_scm:
257 257 :param explicit_scm:
258 258 :param config:
259 259
260 260 :return True: if given path is a valid repository
261 261 """
262 262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
263 263 log.debug('Checking if `%s` is a valid path for repository. '
264 264 'Explicit type: %s', repo_name, explicit_scm)
265 265
266 266 try:
267 267 if explicit_scm:
268 268 detected_scms = [get_scm_backend(explicit_scm)(
269 269 full_path, config=config).alias]
270 270 else:
271 271 detected_scms = get_scm(full_path)
272 272
273 273 if expect_scm:
274 274 return detected_scms[0] == expect_scm
275 275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 276 return True
277 277 except VCSError:
278 278 log.debug('path: %s is not a valid repo !', full_path)
279 279 return False
280 280
281 281
282 282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 283 """
284 284 Returns True if a given path is a repository group, False otherwise
285 285
286 286 :param repo_group_name:
287 287 :param base_path:
288 288 """
289 289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 290 log.debug('Checking if `%s` is a valid path for repository group',
291 291 repo_group_name)
292 292
293 293 # check if it's not a repo
294 294 if is_valid_repo(repo_group_name, base_path):
295 295 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
296 296 return False
297 297
298 298 try:
299 299 # we need to check bare git repos at higher level
300 300 # since we might match branches/hooks/info/objects or possible
301 301 # other things inside bare git repo
302 302 maybe_repo = os.path.dirname(full_path)
303 303 if maybe_repo == base_path:
304 304 # skip root level repo check; we know root location CANNOT BE a repo group
305 305 return False
306 306
307 307 scm_ = get_scm(maybe_repo)
308 308 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
309 309 return False
310 310 except VCSError:
311 311 pass
312 312
313 313 # check if it's a valid path
314 314 if skip_path_check or os.path.isdir(full_path):
315 315 log.debug('path: %s is a valid repo group !', full_path)
316 316 return True
317 317
318 318 log.debug('path: %s is not a valid repo group !', full_path)
319 319 return False
320 320
321 321
322 322 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
323 323 while True:
324 ok = eval(input(prompt))
324 ok = input(prompt)
325 325 if ok.lower() in ('y', 'ye', 'yes'):
326 326 return True
327 327 if ok.lower() in ('n', 'no', 'nop', 'nope'):
328 328 return False
329 329 retries = retries - 1
330 330 if retries < 0:
331 331 raise OSError
332 332 print(complaint)
333 333
334 334 # propagated from mercurial documentation
335 335 ui_sections = [
336 336 'alias', 'auth',
337 337 'decode/encode', 'defaults',
338 338 'diff', 'email',
339 339 'extensions', 'format',
340 340 'merge-patterns', 'merge-tools',
341 341 'hooks', 'http_proxy',
342 342 'smtp', 'patch',
343 343 'paths', 'profiling',
344 344 'server', 'trusted',
345 345 'ui', 'web', ]
346 346
347 347
348 348 def config_data_from_db(clear_session=True, repo=None):
349 349 """
350 350 Read the configuration data from the database and return configuration
351 351 tuples.
352 352 """
353 353 from rhodecode.model.settings import VcsSettingsModel
354 354
355 355 config = []
356 356
357 357 sa = meta.Session()
358 358 settings_model = VcsSettingsModel(repo=repo, sa=sa)
359 359
360 360 ui_settings = settings_model.get_ui_settings()
361 361
362 362 ui_data = []
363 363 for setting in ui_settings:
364 364 if setting.active:
365 365 ui_data.append((setting.section, setting.key, setting.value))
366 366 config.append((
367 367 safe_str(setting.section), safe_str(setting.key),
368 368 safe_str(setting.value)))
369 369 if setting.key == 'push_ssl':
370 370 # force set push_ssl requirement to False, rhodecode
371 371 # handles that
372 372 config.append((
373 373 safe_str(setting.section), safe_str(setting.key), False))
374 374 log.debug(
375 375 'settings ui from db@repo[%s]: %s',
376 376 repo,
377 377 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
378 378 if clear_session:
379 379 meta.Session.remove()
380 380
381 381 # TODO: mikhail: probably it makes no sense to re-read hooks information.
382 382 # It's already there and activated/deactivated
383 383 skip_entries = []
384 384 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
385 385 if 'pull' not in enabled_hook_classes:
386 386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
387 387 if 'push' not in enabled_hook_classes:
388 388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
389 389 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
390 390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
391 391
392 392 config = [entry for entry in config if entry[:2] not in skip_entries]
393 393
394 394 return config
395 395
396 396
397 397 def make_db_config(clear_session=True, repo=None):
398 398 """
399 399 Create a :class:`Config` instance based on the values in the database.
400 400 """
401 401 config = Config()
402 402 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
403 403 for section, option, value in config_data:
404 404 config.set(section, option, value)
405 405 return config
406 406
407 407
408 408 def get_enabled_hook_classes(ui_settings):
409 409 """
410 410 Return the enabled hook classes.
411 411
412 412 :param ui_settings: List of ui_settings as returned
413 413 by :meth:`VcsSettingsModel.get_ui_settings`
414 414
415 415 :return: a list with the enabled hook classes. The order is not guaranteed.
416 416 :rtype: list
417 417 """
418 418 enabled_hooks = []
419 419 active_hook_keys = [
420 420 key for section, key, value, active in ui_settings
421 421 if section == 'hooks' and active]
422 422
423 423 hook_names = {
424 424 RhodeCodeUi.HOOK_PUSH: 'push',
425 425 RhodeCodeUi.HOOK_PULL: 'pull',
426 426 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
427 427 }
428 428
429 429 for key in active_hook_keys:
430 430 hook = hook_names.get(key)
431 431 if hook:
432 432 enabled_hooks.append(hook)
433 433
434 434 return enabled_hooks
435 435
436 436
437 437 def set_rhodecode_config(config):
438 438 """
439 439 Updates pyramid config with new settings from database
440 440
441 441 :param config:
442 442 """
443 443 from rhodecode.model.settings import SettingsModel
444 444 app_settings = SettingsModel().get_all_settings()
445 445
446 446 for k, v in list(app_settings.items()):
447 447 config[k] = v
448 448
449 449
450 450 def get_rhodecode_realm():
451 451 """
452 452 Return the rhodecode realm from database.
453 453 """
454 454 from rhodecode.model.settings import SettingsModel
455 455 realm = SettingsModel().get_setting_by_name('realm')
456 456 return safe_str(realm.app_settings_value)
457 457
458 458
459 459 def get_rhodecode_base_path():
460 460 """
461 461 Returns the base path. The base path is the filesystem path which points
462 462 to the repository store.
463 463 """
464 464
465 465 import rhodecode
466 466 return rhodecode.CONFIG['default_base_path']
467 467
468 468
469 469 def map_groups(path):
470 470 """
471 471 Given a full path to a repository, create all nested groups that this
472 472 repo is inside. This function creates parent-child relationships between
473 473 groups and creates default perms for all new groups.
474 474
475 475 :param paths: full path to repository
476 476 """
477 477 from rhodecode.model.repo_group import RepoGroupModel
478 478 sa = meta.Session()
479 479 groups = path.split(Repository.NAME_SEP)
480 480 parent = None
481 481 group = None
482 482
483 483 # last element is repo in nested groups structure
484 484 groups = groups[:-1]
485 485 rgm = RepoGroupModel(sa)
486 486 owner = User.get_first_super_admin()
487 487 for lvl, group_name in enumerate(groups):
488 488 group_name = '/'.join(groups[:lvl] + [group_name])
489 489 group = RepoGroup.get_by_group_name(group_name)
490 490 desc = '%s group' % group_name
491 491
492 492 # skip folders that are now removed repos
493 493 if REMOVED_REPO_PAT.match(group_name):
494 494 break
495 495
496 496 if group is None:
497 497 log.debug('creating group level: %s group_name: %s',
498 498 lvl, group_name)
499 499 group = RepoGroup(group_name, parent)
500 500 group.group_description = desc
501 501 group.user = owner
502 502 sa.add(group)
503 503 perm_obj = rgm._create_default_perms(group)
504 504 sa.add(perm_obj)
505 505 sa.flush()
506 506
507 507 parent = group
508 508 return group
509 509
510 510
511 511 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
512 512 """
513 513 maps all repos given in initial_repo_list, non existing repositories
514 514 are created, if remove_obsolete is True it also checks for db entries
515 515 that are not in initial_repo_list and removes them.
516 516
517 517 :param initial_repo_list: list of repositories found by scanning methods
518 518 :param remove_obsolete: check for obsolete entries in database
519 519 """
520 520 from rhodecode.model.repo import RepoModel
521 521 from rhodecode.model.repo_group import RepoGroupModel
522 522 from rhodecode.model.settings import SettingsModel
523 523
524 524 sa = meta.Session()
525 525 repo_model = RepoModel()
526 526 user = User.get_first_super_admin()
527 527 added = []
528 528
529 529 # creation defaults
530 530 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
531 531 enable_statistics = defs.get('repo_enable_statistics')
532 532 enable_locking = defs.get('repo_enable_locking')
533 533 enable_downloads = defs.get('repo_enable_downloads')
534 534 private = defs.get('repo_private')
535 535
536 536 for name, repo in list(initial_repo_list.items()):
537 537 group = map_groups(name)
538 538 str_name = safe_str(name)
539 539 db_repo = repo_model.get_by_repo_name(str_name)
540 540 # found repo that is on filesystem not in RhodeCode database
541 541 if not db_repo:
542 542 log.info('repository %s not found, creating now', name)
543 543 added.append(name)
544 544 desc = (repo.description
545 545 if repo.description != 'unknown'
546 546 else '%s repository' % name)
547 547
548 548 db_repo = repo_model._create_repo(
549 549 repo_name=name,
550 550 repo_type=repo.alias,
551 551 description=desc,
552 552 repo_group=getattr(group, 'group_id', None),
553 553 owner=user,
554 554 enable_locking=enable_locking,
555 555 enable_downloads=enable_downloads,
556 556 enable_statistics=enable_statistics,
557 557 private=private,
558 558 state=Repository.STATE_CREATED
559 559 )
560 560 sa.commit()
561 561 # we added that repo just now, and make sure we updated server info
562 562 if db_repo.repo_type == 'git':
563 563 git_repo = db_repo.scm_instance()
564 564 # update repository server-info
565 565 log.debug('Running update server info')
566 566 git_repo._update_server_info()
567 567
568 568 db_repo.update_commit_cache()
569 569
570 570 config = db_repo._config
571 571 config.set('extensions', 'largefiles', '')
572 572 repo = db_repo.scm_instance(config=config)
573 573 repo.install_hooks()
574 574
575 575 removed = []
576 576 if remove_obsolete:
577 577 # remove from database those repositories that are not in the filesystem
578 578 for repo in sa.query(Repository).all():
579 579 if repo.repo_name not in list(initial_repo_list.keys()):
580 580 log.debug("Removing non-existing repository found in db `%s`",
581 581 repo.repo_name)
582 582 try:
583 583 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
584 584 sa.commit()
585 585 removed.append(repo.repo_name)
586 586 except Exception:
587 587 # don't hold further removals on error
588 588 log.error(traceback.format_exc())
589 589 sa.rollback()
590 590
591 591 def splitter(full_repo_name):
592 592 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
593 593 gr_name = None
594 594 if len(_parts) == 2:
595 595 gr_name = _parts[0]
596 596 return gr_name
597 597
598 598 initial_repo_group_list = [splitter(x) for x in
599 599 list(initial_repo_list.keys()) if splitter(x)]
600 600
601 601 # remove from database those repository groups that are not in the
602 602 # filesystem due to parent child relationships we need to delete them
603 603 # in a specific order of most nested first
604 604 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
605 605 def nested_sort(gr):
606 606 return len(gr.split('/'))
607 607 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
608 608 if group_name not in initial_repo_group_list:
609 609 repo_group = RepoGroup.get_by_group_name(group_name)
610 610 if (repo_group.children.all() or
611 611 not RepoGroupModel().check_exist_filesystem(
612 612 group_name=group_name, exc_on_failure=False)):
613 613 continue
614 614
615 615 log.info(
616 616 'Removing non-existing repository group found in db `%s`',
617 617 group_name)
618 618 try:
619 619 RepoGroupModel(sa).delete(group_name, fs_remove=False)
620 620 sa.commit()
621 621 removed.append(group_name)
622 622 except Exception:
623 623 # don't hold further removals on error
624 624 log.exception(
625 625 'Unable to remove repository group `%s`',
626 626 group_name)
627 627 sa.rollback()
628 628 raise
629 629
630 630 return added, removed
631 631
632 632
633 633 def load_rcextensions(root_path):
634 634 import rhodecode
635 635 from rhodecode.config import conf
636 636
637 637 path = os.path.join(root_path)
638 638 sys.path.append(path)
639 639
640 640 try:
641 641 rcextensions = __import__('rcextensions')
642 642 except ImportError:
643 643 if os.path.isdir(os.path.join(path, 'rcextensions')):
644 644 log.warning('Unable to load rcextensions from %s', path)
645 645 rcextensions = None
646 646
647 647 if rcextensions:
648 648 log.info('Loaded rcextensions from %s...', rcextensions)
649 649 rhodecode.EXTENSIONS = rcextensions
650 650
651 651 # Additional mappings that are not present in the pygments lexers
652 652 conf.LANGUAGES_EXTENSIONS_MAP.update(
653 653 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
654 654
655 655
656 656 def get_custom_lexer(extension):
657 657 """
658 658 returns a custom lexer if it is defined in rcextensions module, or None
659 659 if there's no custom lexer defined
660 660 """
661 661 import rhodecode
662 662 from pygments import lexers
663 663
664 664 # custom override made by RhodeCode
665 665 if extension in ['mako']:
666 666 return lexers.get_lexer_by_name('html+mako')
667 667
668 668 # check if we didn't define this extension as other lexer
669 669 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
670 670 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
671 671 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
672 672 return lexers.get_lexer_by_name(_lexer_name)
673 673
674 674
675 675 #==============================================================================
676 676 # TEST FUNCTIONS AND CREATORS
677 677 #==============================================================================
678 678 def create_test_index(repo_location, config):
679 679 """
680 680 Makes default test index.
681 681 """
682 682 try:
683 683 import rc_testdata
684 684 except ImportError:
685 685 raise ImportError('Failed to import rc_testdata, '
686 686 'please make sure this package is installed from requirements_test.txt')
687 687 rc_testdata.extract_search_index(
688 688 'vcs_search_index', os.path.dirname(config['search.location']))
689 689
690 690
691 691 def create_test_directory(test_path):
692 692 """
693 693 Create test directory if it doesn't exist.
694 694 """
695 695 if not os.path.isdir(test_path):
696 696 log.debug('Creating testdir %s', test_path)
697 697 os.makedirs(test_path)
698 698
699 699
700 700 def create_test_database(test_path, config):
701 701 """
702 702 Makes a fresh database.
703 703 """
704 704 from rhodecode.lib.db_manage import DbManage
705 705 from rhodecode.lib.utils2 import get_encryption_key
706 706
707 707 # PART ONE create db
708 708 dbconf = config['sqlalchemy.db1.url']
709 709 enc_key = get_encryption_key(config)
710 710
711 711 log.debug('making test db %s', dbconf)
712 712
713 713 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
714 714 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
715 715 dbmanage.create_tables(override=True)
716 716 dbmanage.set_db_version()
717 717 # for tests dynamically set new root paths based on generated content
718 718 dbmanage.create_settings(dbmanage.config_prompt(test_path))
719 719 dbmanage.create_default_user()
720 720 dbmanage.create_test_admin_and_users()
721 721 dbmanage.create_permissions()
722 722 dbmanage.populate_default_permissions()
723 723 Session().commit()
724 724
725 725
726 726 def create_test_repositories(test_path, config):
727 727 """
728 728 Creates test repositories in the temporary directory. Repositories are
729 729 extracted from archives within the rc_testdata package.
730 730 """
731 731 import rc_testdata
732 732 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
733 733
734 734 log.debug('making test vcs repositories')
735 735
736 736 idx_path = config['search.location']
737 737 data_path = config['cache_dir']
738 738
739 739 # clean index and data
740 740 if idx_path and os.path.exists(idx_path):
741 741 log.debug('remove %s', idx_path)
742 742 shutil.rmtree(idx_path)
743 743
744 744 if data_path and os.path.exists(data_path):
745 745 log.debug('remove %s', data_path)
746 746 shutil.rmtree(data_path)
747 747
748 748 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
749 749 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
750 750
751 751 # Note: Subversion is in the process of being integrated with the system,
752 752 # until we have a properly packed version of the test svn repository, this
753 753 # tries to copy over the repo from a package "rc_testdata"
754 754 svn_repo_path = rc_testdata.get_svn_repo_archive()
755 755 with tarfile.open(svn_repo_path) as tar:
756 756 tar.extractall(jn(test_path, SVN_REPO))
757 757
758 758
759 759 def password_changed(auth_user, session):
760 760 # Never report password change in case of default user or anonymous user.
761 761 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
762 762 return False
763 763
764 764 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
765 765 rhodecode_user = session.get('rhodecode_user', {})
766 766 session_password_hash = rhodecode_user.get('password', '')
767 767 return password_hash != session_password_hash
768 768
769 769
770 770 def read_opensource_licenses():
771 771 global _license_cache
772 772
773 773 if not _license_cache:
774 774 licenses = pkg_resources.resource_string(
775 775 'rhodecode', 'config/licenses.json')
776 776 _license_cache = json.loads(licenses)
777 777
778 778 return _license_cache
779 779
780 780
781 781 def generate_platform_uuid():
782 782 """
783 783 Generates platform UUID based on it's name
784 784 """
785 785 import platform
786 786
787 787 try:
788 788 uuid_list = [platform.platform()]
789 789 return sha256_safe(':'.join(uuid_list))
790 790 except Exception as e:
791 791 log.error('Failed to generate host uuid: %s', e)
792 792 return 'UNDEFINED'
793 793
794 794
795 795 def send_test_email(recipients, email_body='TEST EMAIL'):
796 796 """
797 797 Simple code for generating test emails.
798 798 Usage::
799 799
800 800 from rhodecode.lib import utils
801 801 utils.send_test_email()
802 802 """
803 803 from rhodecode.lib.celerylib import tasks, run_task
804 804
805 805 email_body = email_body_plaintext = email_body
806 806 subject = f'SUBJECT FROM: {socket.gethostname()}'
807 807 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
General Comments 0
You need to be logged in to leave comments. Login now