##// END OF EJS Templates
Added optional flag to make_ui to not clean sqlalchemy Session....
marcink -
r2717:dd240b2b beta
parent child Browse files
Show More
@@ -1,668 +1,668 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.repos_group import ReposGroupModel
57 57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 58 from rhodecode.lib.vcs.utils.fakemod import create_module
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63 63
64 64
65 65 def recursive_replace(str_, replace=' '):
66 66 """
67 67 Recursive replace of given sign to just one instance
68 68
69 69 :param str_: given string
70 70 :param replace: char to find and replace multiple instances
71 71
72 72 Examples::
73 73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 74 'Mighty-Mighty-Bo-sstones'
75 75 """
76 76
77 77 if str_.find(replace * 2) == -1:
78 78 return str_
79 79 else:
80 80 str_ = str_.replace(replace * 2, replace)
81 81 return recursive_replace(str_, replace)
82 82
83 83
84 84 def repo_name_slug(value):
85 85 """
86 86 Return slug of name of repository
87 87 This function is called on each creation/modification
88 88 of repository to prevent bad names in repo
89 89 """
90 90
91 91 slug = remove_formatting(value)
92 92 slug = strip_tags(slug)
93 93
94 94 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 95 slug = slug.replace(c, '-')
96 96 slug = recursive_replace(slug, '-')
97 97 slug = collapse(slug, '-')
98 98 return slug
99 99
100 100
101 101 def get_repo_slug(request):
102 102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 103 if _repo:
104 104 _repo = _repo.rstrip('/')
105 105 return _repo
106 106
107 107
108 108 def get_repos_group_slug(request):
109 109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 110 if _group:
111 111 _group = _group.rstrip('/')
112 112 return _group
113 113
114 114
115 115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 116 """
117 117 Action logger for various actions made by users
118 118
119 119 :param user: user that made this action, can be a unique username string or
120 120 object containing user_id attribute
121 121 :param action: action to log, should be on of predefined unique actions for
122 122 easy translations
123 123 :param repo: string name of repository or object containing repo_id,
124 124 that action was made on
125 125 :param ipaddr: optional ip address from what the action was made
126 126 :param sa: optional sqlalchemy session
127 127
128 128 """
129 129
130 130 if not sa:
131 131 sa = meta.Session()
132 132
133 133 try:
134 134 if hasattr(user, 'user_id'):
135 135 user_obj = user
136 136 elif isinstance(user, basestring):
137 137 user_obj = User.get_by_username(user)
138 138 else:
139 139 raise Exception('You have to provide user object or username')
140 140
141 141 if hasattr(repo, 'repo_id'):
142 142 repo_obj = Repository.get(repo.repo_id)
143 143 repo_name = repo_obj.repo_name
144 144 elif isinstance(repo, basestring):
145 145 repo_name = repo.lstrip('/')
146 146 repo_obj = Repository.get_by_repo_name(repo_name)
147 147 else:
148 148 repo_obj = None
149 149 repo_name = ''
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository = repo_obj
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 206 def is_valid_repo(repo_name, base_path, scm=None):
207 207 """
208 208 Returns True if given path is a valid repository False otherwise.
209 209 If scm param is given also compare if given scm is the same as expected
210 210 from scm parameter
211 211
212 212 :param repo_name:
213 213 :param base_path:
214 214 :param scm:
215 215
216 216 :return True: if given path is a valid repository
217 217 """
218 218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 219
220 220 try:
221 221 scm_ = get_scm(full_path)
222 222 if scm:
223 223 return scm_[0] == scm
224 224 return True
225 225 except VCSError:
226 226 return False
227 227
228 228
229 229 def is_valid_repos_group(repos_group_name, base_path):
230 230 """
231 231 Returns True if given path is a repos group False otherwise
232 232
233 233 :param repo_name:
234 234 :param base_path:
235 235 """
236 236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 237
238 238 # check if it's not a repo
239 239 if is_valid_repo(repos_group_name, base_path):
240 240 return False
241 241
242 242 try:
243 243 # we need to check bare git repos at higher level
244 244 # since we might match branches/hooks/info/objects or possible
245 245 # other things inside bare git repo
246 246 get_scm(os.path.dirname(full_path))
247 247 return False
248 248 except VCSError:
249 249 pass
250 250
251 251 # check if it's a valid path
252 252 if os.path.isdir(full_path):
253 253 return True
254 254
255 255 return False
256 256
257 257
258 258 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
259 259 while True:
260 260 ok = raw_input(prompt)
261 261 if ok in ('y', 'ye', 'yes'):
262 262 return True
263 263 if ok in ('n', 'no', 'nop', 'nope'):
264 264 return False
265 265 retries = retries - 1
266 266 if retries < 0:
267 267 raise IOError
268 268 print complaint
269 269
270 270 #propagated from mercurial documentation
271 271 ui_sections = ['alias', 'auth',
272 272 'decode/encode', 'defaults',
273 273 'diff', 'email',
274 274 'extensions', 'format',
275 275 'merge-patterns', 'merge-tools',
276 276 'hooks', 'http_proxy',
277 277 'smtp', 'patch',
278 278 'paths', 'profiling',
279 279 'server', 'trusted',
280 280 'ui', 'web', ]
281 281
282 282
283 def make_ui(read_from='file', path=None, checkpaths=True):
283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 284 """
285 285 A function that will read python rc files or database
286 286 and make an mercurial ui object from read options
287 287
288 288 :param path: path to mercurial config file
289 289 :param checkpaths: check the path
290 290 :param read_from: read from 'file' or 'db'
291 291 """
292 292
293 293 baseui = ui.ui()
294 294
295 295 # clean the baseui object
296 296 baseui._ocfg = config.config()
297 297 baseui._ucfg = config.config()
298 298 baseui._tcfg = config.config()
299 299
300 300 if read_from == 'file':
301 301 if not os.path.isfile(path):
302 302 log.debug('hgrc file is not present at %s skipping...' % path)
303 303 return False
304 304 log.debug('reading hgrc from %s' % path)
305 305 cfg = config.config()
306 306 cfg.read(path)
307 307 for section in ui_sections:
308 308 for k, v in cfg.items(section):
309 309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 310 baseui.setconfig(section, k, v)
311 311
312 312 elif read_from == 'db':
313 313 sa = meta.Session()
314 314 ret = sa.query(RhodeCodeUi)\
315 315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 316 .all()
317 317
318 318 hg_ui = ret
319 319 for ui_ in hg_ui:
320 320 if ui_.ui_active:
321 321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 322 ui_.ui_key, ui_.ui_value)
323 323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
324 324 if ui_.ui_key == 'push_ssl':
325 325 # force set push_ssl requirement to False, rhodecode
326 326 # handles that
327 327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
328
329 meta.Session.remove()
328 if clear_session:
329 meta.Session.remove()
330 330 return baseui
331 331
332 332
333 333 def set_rhodecode_config(config):
334 334 """
335 335 Updates pylons config with new settings from database
336 336
337 337 :param config:
338 338 """
339 339 hgsettings = RhodeCodeSetting.get_app_settings()
340 340
341 341 for k, v in hgsettings.items():
342 342 config[k] = v
343 343
344 344
345 345 def invalidate_cache(cache_key, *args):
346 346 """
347 347 Puts cache invalidation task into db for
348 348 further global cache invalidation
349 349 """
350 350
351 351 from rhodecode.model.scm import ScmModel
352 352
353 353 if cache_key.startswith('get_repo_cached_'):
354 354 name = cache_key.split('get_repo_cached_')[-1]
355 355 ScmModel().mark_for_invalidation(name)
356 356
357 357
358 358 def map_groups(path):
359 359 """
360 360 Given a full path to a repository, create all nested groups that this
361 361 repo is inside. This function creates parent-child relationships between
362 362 groups and creates default perms for all new groups.
363 363
364 364 :param paths: full path to repository
365 365 """
366 366 sa = meta.Session()
367 367 groups = path.split(Repository.url_sep())
368 368 parent = None
369 369 group = None
370 370
371 371 # last element is repo in nested groups structure
372 372 groups = groups[:-1]
373 373 rgm = ReposGroupModel(sa)
374 374 for lvl, group_name in enumerate(groups):
375 375 group_name = '/'.join(groups[:lvl] + [group_name])
376 376 group = RepoGroup.get_by_group_name(group_name)
377 377 desc = '%s group' % group_name
378 378
379 379 # skip folders that are now removed repos
380 380 if REMOVED_REPO_PAT.match(group_name):
381 381 break
382 382
383 383 if group is None:
384 384 log.debug('creating group level: %s group_name: %s' % (lvl,
385 385 group_name))
386 386 group = RepoGroup(group_name, parent)
387 387 group.group_description = desc
388 388 sa.add(group)
389 389 rgm._create_default_perms(group)
390 390 sa.flush()
391 391 parent = group
392 392 return group
393 393
394 394
395 395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 396 install_git_hook=False):
397 397 """
398 398 maps all repos given in initial_repo_list, non existing repositories
399 399 are created, if remove_obsolete is True it also check for db entries
400 400 that are not in initial_repo_list and removes them.
401 401
402 402 :param initial_repo_list: list of repositories found by scanning methods
403 403 :param remove_obsolete: check for obsolete entries in database
404 404 :param install_git_hook: if this is True, also check and install githook
405 405 for a repo if missing
406 406 """
407 407 from rhodecode.model.repo import RepoModel
408 408 from rhodecode.model.scm import ScmModel
409 409 sa = meta.Session()
410 410 rm = RepoModel()
411 411 user = sa.query(User).filter(User.admin == True).first()
412 412 if user is None:
413 413 raise Exception('Missing administrative account !')
414 414 added = []
415 415
416 416 for name, repo in initial_repo_list.items():
417 417 group = map_groups(name)
418 418 db_repo = rm.get_by_repo_name(name)
419 419 # found repo that is on filesystem not in RhodeCode database
420 420 if not db_repo:
421 421 log.info('repository %s not found creating now' % name)
422 422 added.append(name)
423 423 desc = (repo.description
424 424 if repo.description != 'unknown'
425 425 else '%s repository' % name)
426 426 new_repo = rm.create_repo(
427 427 repo_name=name,
428 428 repo_type=repo.alias,
429 429 description=desc,
430 430 repos_group=getattr(group, 'group_id', None),
431 431 owner=user,
432 432 just_db=True
433 433 )
434 434 # we added that repo just now, and make sure it has githook
435 435 # installed
436 436 if new_repo.repo_type == 'git':
437 437 ScmModel().install_git_hook(new_repo.scm_instance)
438 438 elif install_git_hook:
439 439 if db_repo.repo_type == 'git':
440 440 ScmModel().install_git_hook(db_repo.scm_instance)
441 441 sa.commit()
442 442 removed = []
443 443 if remove_obsolete:
444 444 # remove from database those repositories that are not in the filesystem
445 445 for repo in sa.query(Repository).all():
446 446 if repo.repo_name not in initial_repo_list.keys():
447 447 log.debug("Removing non existing repository found in db `%s`" %
448 448 repo.repo_name)
449 449 try:
450 450 sa.delete(repo)
451 451 sa.commit()
452 452 removed.append(repo.repo_name)
453 453 except:
454 454 #don't hold further removals on error
455 455 log.error(traceback.format_exc())
456 456 sa.rollback()
457 457
458 458 # clear cache keys
459 459 log.debug("Clearing cache keys now...")
460 460 CacheInvalidation.clear_cache()
461 461 sa.commit()
462 462 return added, removed
463 463
464 464
465 465 # set cache regions for beaker so celery can utilise it
466 466 def add_cache(settings):
467 467 cache_settings = {'regions': None}
468 468 for key in settings.keys():
469 469 for prefix in ['beaker.cache.', 'cache.']:
470 470 if key.startswith(prefix):
471 471 name = key.split(prefix)[1].strip()
472 472 cache_settings[name] = settings[key].strip()
473 473 if cache_settings['regions']:
474 474 for region in cache_settings['regions'].split(','):
475 475 region = region.strip()
476 476 region_settings = {}
477 477 for key, value in cache_settings.items():
478 478 if key.startswith(region):
479 479 region_settings[key.split('.')[1]] = value
480 480 region_settings['expire'] = int(region_settings.get('expire',
481 481 60))
482 482 region_settings.setdefault('lock_dir',
483 483 cache_settings.get('lock_dir'))
484 484 region_settings.setdefault('data_dir',
485 485 cache_settings.get('data_dir'))
486 486
487 487 if 'type' not in region_settings:
488 488 region_settings['type'] = cache_settings.get('type',
489 489 'memory')
490 490 beaker.cache.cache_regions[region] = region_settings
491 491
492 492
493 493 def load_rcextensions(root_path):
494 494 import rhodecode
495 495 from rhodecode.config import conf
496 496
497 497 path = os.path.join(root_path, 'rcextensions', '__init__.py')
498 498 if os.path.isfile(path):
499 499 rcext = create_module('rc', path)
500 500 EXT = rhodecode.EXTENSIONS = rcext
501 501 log.debug('Found rcextensions now loading %s...' % rcext)
502 502
503 503 # Additional mappings that are not present in the pygments lexers
504 504 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
505 505
506 506 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
507 507
508 508 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
509 509 log.debug('settings custom INDEX_EXTENSIONS')
510 510 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
511 511
512 512 #ADDITIONAL MAPPINGS
513 513 log.debug('adding extra into INDEX_EXTENSIONS')
514 514 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
515 515
516 516
517 517 #==============================================================================
518 518 # TEST FUNCTIONS AND CREATORS
519 519 #==============================================================================
520 520 def create_test_index(repo_location, config, full_index):
521 521 """
522 522 Makes default test index
523 523
524 524 :param config: test config
525 525 :param full_index:
526 526 """
527 527
528 528 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
529 529 from rhodecode.lib.pidlock import DaemonLock, LockHeld
530 530
531 531 repo_location = repo_location
532 532
533 533 index_location = os.path.join(config['app_conf']['index_dir'])
534 534 if not os.path.exists(index_location):
535 535 os.makedirs(index_location)
536 536
537 537 try:
538 538 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
539 539 WhooshIndexingDaemon(index_location=index_location,
540 540 repo_location=repo_location)\
541 541 .run(full_index=full_index)
542 542 l.release()
543 543 except LockHeld:
544 544 pass
545 545
546 546
547 547 def create_test_env(repos_test_path, config):
548 548 """
549 549 Makes a fresh database and
550 550 install test repository into tmp dir
551 551 """
552 552 from rhodecode.lib.db_manage import DbManage
553 553 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
554 554
555 555 # PART ONE create db
556 556 dbconf = config['sqlalchemy.db1.url']
557 557 log.debug('making test db %s' % dbconf)
558 558
559 559 # create test dir if it doesn't exist
560 560 if not os.path.isdir(repos_test_path):
561 561 log.debug('Creating testdir %s' % repos_test_path)
562 562 os.makedirs(repos_test_path)
563 563
564 564 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
565 565 tests=True)
566 566 dbmanage.create_tables(override=True)
567 567 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
568 568 dbmanage.create_default_user()
569 569 dbmanage.admin_prompt()
570 570 dbmanage.create_permissions()
571 571 dbmanage.populate_default_permissions()
572 572 Session().commit()
573 573 # PART TWO make test repo
574 574 log.debug('making test vcs repositories')
575 575
576 576 idx_path = config['app_conf']['index_dir']
577 577 data_path = config['app_conf']['cache_dir']
578 578
579 579 #clean index and data
580 580 if idx_path and os.path.exists(idx_path):
581 581 log.debug('remove %s' % idx_path)
582 582 shutil.rmtree(idx_path)
583 583
584 584 if data_path and os.path.exists(data_path):
585 585 log.debug('remove %s' % data_path)
586 586 shutil.rmtree(data_path)
587 587
588 588 #CREATE DEFAULT TEST REPOS
589 589 cur_dir = dn(dn(abspath(__file__)))
590 590 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
591 591 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
592 592 tar.close()
593 593
594 594 cur_dir = dn(dn(abspath(__file__)))
595 595 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
596 596 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
597 597 tar.close()
598 598
599 599 #LOAD VCS test stuff
600 600 from rhodecode.tests.vcs import setup_package
601 601 setup_package()
602 602
603 603
604 604 #==============================================================================
605 605 # PASTER COMMANDS
606 606 #==============================================================================
607 607 class BasePasterCommand(Command):
608 608 """
609 609 Abstract Base Class for paster commands.
610 610
611 611 The celery commands are somewhat aggressive about loading
612 612 celery.conf, and since our module sets the `CELERY_LOADER`
613 613 environment variable to our loader, we have to bootstrap a bit and
614 614 make sure we've had a chance to load the pylons config off of the
615 615 command line, otherwise everything fails.
616 616 """
617 617 min_args = 1
618 618 min_args_error = "Please provide a paster config file as an argument."
619 619 takes_config_file = 1
620 620 requires_config_file = True
621 621
622 622 def notify_msg(self, msg, log=False):
623 623 """Make a notification to user, additionally if logger is passed
624 624 it logs this action using given logger
625 625
626 626 :param msg: message that will be printed to user
627 627 :param log: logging instance, to use to additionally log this message
628 628
629 629 """
630 630 if log and isinstance(log, logging):
631 631 log(msg)
632 632
633 633 def run(self, args):
634 634 """
635 635 Overrides Command.run
636 636
637 637 Checks for a config file argument and loads it.
638 638 """
639 639 if len(args) < self.min_args:
640 640 raise BadCommand(
641 641 self.min_args_error % {'min_args': self.min_args,
642 642 'actual_args': len(args)})
643 643
644 644 # Decrement because we're going to lob off the first argument.
645 645 # @@ This is hacky
646 646 self.min_args -= 1
647 647 self.bootstrap_config(args[0])
648 648 self.update_parser()
649 649 return super(BasePasterCommand, self).run(args[1:])
650 650
651 651 def update_parser(self):
652 652 """
653 653 Abstract method. Allows for the class's parser to be updated
654 654 before the superclass's `run` method is called. Necessary to
655 655 allow options/arguments to be passed through to the underlying
656 656 celery command.
657 657 """
658 658 raise NotImplementedError("Abstract Method.")
659 659
660 660 def bootstrap_config(self, conf):
661 661 """
662 662 Loads the pylons configuration.
663 663 """
664 664 from pylons import config as pylonsconfig
665 665
666 666 self.path_to_ini_file = os.path.realpath(conf)
667 667 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
668 668 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,601 +1,601 b''
1 1 """
2 2 Set of generic validators
3 3 """
4 4 import os
5 5 import re
6 6 import formencode
7 7 import logging
8 8 from pylons.i18n.translation import _
9 9 from webhelpers.pylonslib.secure_form import authentication_token
10 10
11 11 from formencode.validators import (
12 12 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 13 )
14 14 from rhodecode.lib.utils import repo_name_slug
15 15 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User
16 16 from rhodecode.lib.exceptions import LdapImportError
17 17 from rhodecode.config.routing import ADMIN_PREFIX
18 18 # silence warnings and pylint
19 19 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set
20 20
21 21 log = logging.getLogger(__name__)
22 22
23 23
24 24 class StateObj(object):
25 25 """
26 26 this is needed to translate the messages using _() in validators
27 27 """
28 28 _ = staticmethod(_)
29 29
30 30
31 31 def M(self, key, state=None, **kwargs):
32 32 """
33 33 returns string from self.message based on given key,
34 34 passed kw params are used to substitute %(named)s params inside
35 35 translated strings
36 36
37 37 :param msg:
38 38 :param state:
39 39 """
40 40 if state is None:
41 41 state = StateObj()
42 42 else:
43 43 state._ = staticmethod(_)
44 44 #inject validator into state object
45 45 return self.message(key, state, **kwargs)
46 46
47 47
48 48 def ValidUsername(edit=False, old_data={}):
49 49 class _validator(formencode.validators.FancyValidator):
50 50 messages = {
51 51 'username_exists': _(u'Username "%(username)s" already exists'),
52 52 'system_invalid_username':
53 53 _(u'Username "%(username)s" is forbidden'),
54 54 'invalid_username':
55 55 _(u'Username may only contain alphanumeric characters '
56 56 'underscores, periods or dashes and must begin with '
57 57 'alphanumeric character')
58 58 }
59 59
60 60 def validate_python(self, value, state):
61 61 if value in ['default', 'new_user']:
62 62 msg = M(self, 'system_invalid_username', state, username=value)
63 63 raise formencode.Invalid(msg, value, state)
64 64 #check if user is unique
65 65 old_un = None
66 66 if edit:
67 67 old_un = User.get(old_data.get('user_id')).username
68 68
69 69 if old_un != value or not edit:
70 70 if User.get_by_username(value, case_insensitive=True):
71 71 msg = M(self, 'username_exists', state, username=value)
72 72 raise formencode.Invalid(msg, value, state)
73 73
74 74 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
75 75 msg = M(self, 'invalid_username', state)
76 76 raise formencode.Invalid(msg, value, state)
77 77 return _validator
78 78
79 79
80 80 def ValidRepoUser():
81 81 class _validator(formencode.validators.FancyValidator):
82 82 messages = {
83 83 'invalid_username': _(u'Username %(username)s is not valid')
84 84 }
85 85
86 86 def validate_python(self, value, state):
87 87 try:
88 88 User.query().filter(User.active == True)\
89 89 .filter(User.username == value).one()
90 90 except Exception:
91 91 msg = M(self, 'invalid_username', state, username=value)
92 92 raise formencode.Invalid(msg, value, state,
93 93 error_dict=dict(username=msg)
94 94 )
95 95
96 96 return _validator
97 97
98 98
99 99 def ValidUsersGroup(edit=False, old_data={}):
100 100 class _validator(formencode.validators.FancyValidator):
101 101 messages = {
102 102 'invalid_group': _(u'Invalid users group name'),
103 103 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
104 104 'invalid_usersgroup_name':
105 105 _(u'users group name may only contain alphanumeric '
106 106 'characters underscores, periods or dashes and must begin '
107 107 'with alphanumeric character')
108 108 }
109 109
110 110 def validate_python(self, value, state):
111 111 if value in ['default']:
112 112 msg = M(self, 'invalid_group', state)
113 113 raise formencode.Invalid(msg, value, state,
114 114 error_dict=dict(users_group_name=msg)
115 115 )
116 116 #check if group is unique
117 117 old_ugname = None
118 118 if edit:
119 119 old_id = old_data.get('users_group_id')
120 120 old_ugname = UsersGroup.get(old_id).users_group_name
121 121
122 122 if old_ugname != value or not edit:
123 123 is_existing_group = UsersGroup.get_by_group_name(value,
124 124 case_insensitive=True)
125 125 if is_existing_group:
126 126 msg = M(self, 'group_exist', state, usersgroup=value)
127 127 raise formencode.Invalid(msg, value, state,
128 128 error_dict=dict(users_group_name=msg)
129 129 )
130 130
131 131 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
132 132 msg = M(self, 'invalid_usersgroup_name', state)
133 133 raise formencode.Invalid(msg, value, state,
134 134 error_dict=dict(users_group_name=msg)
135 135 )
136 136
137 137 return _validator
138 138
139 139
140 140 def ValidReposGroup(edit=False, old_data={}):
141 141 class _validator(formencode.validators.FancyValidator):
142 142 messages = {
143 143 'group_parent_id': _(u'Cannot assign this group as parent'),
144 144 'group_exists': _(u'Group "%(group_name)s" already exists'),
145 145 'repo_exists':
146 146 _(u'Repository with name "%(group_name)s" already exists')
147 147 }
148 148
149 149 def validate_python(self, value, state):
150 150 # TODO WRITE VALIDATIONS
151 151 group_name = value.get('group_name')
152 152 group_parent_id = value.get('group_parent_id')
153 153
154 154 # slugify repo group just in case :)
155 155 slug = repo_name_slug(group_name)
156 156
157 157 # check for parent of self
158 158 parent_of_self = lambda: (
159 159 old_data['group_id'] == int(group_parent_id)
160 160 if group_parent_id else False
161 161 )
162 162 if edit and parent_of_self():
163 163 msg = M(self, 'group_parent_id', state)
164 164 raise formencode.Invalid(msg, value, state,
165 165 error_dict=dict(group_parent_id=msg)
166 166 )
167 167
168 168 old_gname = None
169 169 if edit:
170 170 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
171 171
172 172 if old_gname != group_name or not edit:
173 173
174 174 # check group
175 175 gr = RepoGroup.query()\
176 176 .filter(RepoGroup.group_name == slug)\
177 177 .filter(RepoGroup.group_parent_id == group_parent_id)\
178 178 .scalar()
179 179
180 180 if gr:
181 181 msg = M(self, 'group_exists', state, group_name=slug)
182 182 raise formencode.Invalid(msg, value, state,
183 183 error_dict=dict(group_name=msg)
184 184 )
185 185
186 186 # check for same repo
187 187 repo = Repository.query()\
188 188 .filter(Repository.repo_name == slug)\
189 189 .scalar()
190 190
191 191 if repo:
192 192 msg = M(self, 'repo_exists', state, group_name=slug)
193 193 raise formencode.Invalid(msg, value, state,
194 194 error_dict=dict(group_name=msg)
195 195 )
196 196
197 197 return _validator
198 198
199 199
200 200 def ValidPassword():
201 201 class _validator(formencode.validators.FancyValidator):
202 202 messages = {
203 203 'invalid_password':
204 204 _(u'Invalid characters (non-ascii) in password')
205 205 }
206 206
207 207 def validate_python(self, value, state):
208 208 try:
209 209 (value or '').decode('ascii')
210 210 except UnicodeError:
211 211 msg = M(self, 'invalid_password', state)
212 212 raise formencode.Invalid(msg, value, state,)
213 213 return _validator
214 214
215 215
216 216 def ValidPasswordsMatch():
217 217 class _validator(formencode.validators.FancyValidator):
218 218 messages = {
219 219 'password_mismatch': _(u'Passwords do not match'),
220 220 }
221 221
222 222 def validate_python(self, value, state):
223 223
224 224 pass_val = value.get('password') or value.get('new_password')
225 225 if pass_val != value['password_confirmation']:
226 226 msg = M(self, 'password_mismatch', state)
227 227 raise formencode.Invalid(msg, value, state,
228 228 error_dict=dict(password_confirmation=msg)
229 229 )
230 230 return _validator
231 231
232 232
233 233 def ValidAuth():
234 234 class _validator(formencode.validators.FancyValidator):
235 235 messages = {
236 236 'invalid_password': _(u'invalid password'),
237 237 'invalid_username': _(u'invalid user name'),
238 238 'disabled_account': _(u'Your account is disabled')
239 239 }
240 240
241 241 def validate_python(self, value, state):
242 242 from rhodecode.lib.auth import authenticate
243 243
244 244 password = value['password']
245 245 username = value['username']
246 246
247 247 if not authenticate(username, password):
248 248 user = User.get_by_username(username)
249 249 if user and user.active is False:
250 250 log.warning('user %s is disabled' % username)
251 251 msg = M(self, 'disabled_account', state)
252 252 raise formencode.Invalid(msg, value, state,
253 253 error_dict=dict(username=msg)
254 254 )
255 255 else:
256 256 log.warning('user %s failed to authenticate' % username)
257 257 msg = M(self, 'invalid_username', state)
258 258 msg2 = M(self, 'invalid_password', state)
259 259 raise formencode.Invalid(msg, value, state,
260 260 error_dict=dict(username=msg, password=msg2)
261 261 )
262 262 return _validator
263 263
264 264
265 265 def ValidAuthToken():
266 266 class _validator(formencode.validators.FancyValidator):
267 267 messages = {
268 268 'invalid_token': _(u'Token mismatch')
269 269 }
270 270
271 271 def validate_python(self, value, state):
272 272 if value != authentication_token():
273 273 msg = M(self, 'invalid_token', state)
274 274 raise formencode.Invalid(msg, value, state)
275 275 return _validator
276 276
277 277
278 278 def ValidRepoName(edit=False, old_data={}):
279 279 class _validator(formencode.validators.FancyValidator):
280 280 messages = {
281 281 'invalid_repo_name':
282 282 _(u'Repository name %(repo)s is disallowed'),
283 283 'repository_exists':
284 284 _(u'Repository named %(repo)s already exists'),
285 285 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
286 286 'exists in group "%(group)s"'),
287 287 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
288 288 'already exists')
289 289 }
290 290
291 291 def _to_python(self, value, state):
292 292 repo_name = repo_name_slug(value.get('repo_name', ''))
293 293 repo_group = value.get('repo_group')
294 294 if repo_group:
295 295 gr = RepoGroup.get(repo_group)
296 296 group_path = gr.full_path
297 297 group_name = gr.group_name
298 298 # value needs to be aware of group name in order to check
299 299 # db key This is an actual just the name to store in the
300 300 # database
301 301 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
302 302 else:
303 303 group_name = group_path = ''
304 304 repo_name_full = repo_name
305 305
306 306 value['repo_name'] = repo_name
307 307 value['repo_name_full'] = repo_name_full
308 308 value['group_path'] = group_path
309 309 value['group_name'] = group_name
310 310 return value
311 311
312 312 def validate_python(self, value, state):
313 313
314 314 repo_name = value.get('repo_name')
315 315 repo_name_full = value.get('repo_name_full')
316 316 group_path = value.get('group_path')
317 317 group_name = value.get('group_name')
318 318
319 319 if repo_name in [ADMIN_PREFIX, '']:
320 320 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
321 321 raise formencode.Invalid(msg, value, state,
322 322 error_dict=dict(repo_name=msg)
323 323 )
324 324
325 325 rename = old_data.get('repo_name') != repo_name_full
326 326 create = not edit
327 327 if rename or create:
328 328
329 329 if group_path != '':
330 330 if Repository.get_by_repo_name(repo_name_full):
331 331 msg = M(self, 'repository_in_group_exists', state,
332 332 repo=repo_name, group=group_name)
333 333 raise formencode.Invalid(msg, value, state,
334 334 error_dict=dict(repo_name=msg)
335 335 )
336 336 elif RepoGroup.get_by_group_name(repo_name_full):
337 337 msg = M(self, 'same_group_exists', state,
338 338 repo=repo_name)
339 339 raise formencode.Invalid(msg, value, state,
340 340 error_dict=dict(repo_name=msg)
341 341 )
342 342
343 343 elif Repository.get_by_repo_name(repo_name_full):
344 344 msg = M(self, 'repository_exists', state,
345 345 repo=repo_name)
346 346 raise formencode.Invalid(msg, value, state,
347 347 error_dict=dict(repo_name=msg)
348 348 )
349 349 return value
350 350 return _validator
351 351
352 352
353 353 def ValidForkName(*args, **kwargs):
354 354 return ValidRepoName(*args, **kwargs)
355 355
356 356
357 357 def SlugifyName():
358 358 class _validator(formencode.validators.FancyValidator):
359 359
360 360 def _to_python(self, value, state):
361 361 return repo_name_slug(value)
362 362
363 363 def validate_python(self, value, state):
364 364 pass
365 365
366 366 return _validator
367 367
368 368
369 369 def ValidCloneUri():
370 370 from rhodecode.lib.utils import make_ui
371 371
372 372 def url_handler(repo_type, url, ui=None):
373 373 if repo_type == 'hg':
374 374 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
375 375 from mercurial.httppeer import httppeer
376 376 if url.startswith('http'):
377 377 ## initially check if it's at least the proper URL
378 378 ## or does it pass basic auth
379 379 MercurialRepository._check_url(url)
380 httppeer(make_ui('db'), url)._capabilities()
380 httppeer(ui, url)._capabilities()
381 381 elif url.startswith('svn+http'):
382 382 from hgsubversion.svnrepo import svnremoterepo
383 svnremoterepo(make_ui('db'), url).capabilities
383 svnremoterepo(ui, url).capabilities
384 384 elif url.startswith('git+http'):
385 385 raise NotImplementedError()
386 386
387 387 elif repo_type == 'git':
388 388 from rhodecode.lib.vcs.backends.git.repository import GitRepository
389 389 if url.startswith('http'):
390 390 ## initially check if it's at least the proper URL
391 391 ## or does it pass basic auth
392 392 GitRepository._check_url(url)
393 393 elif url.startswith('svn+http'):
394 394 raise NotImplementedError()
395 395 elif url.startswith('hg+http'):
396 396 raise NotImplementedError()
397 397
398 398 class _validator(formencode.validators.FancyValidator):
399 399 messages = {
400 400 'clone_uri': _(u'invalid clone url'),
401 401 'invalid_clone_uri': _(u'Invalid clone url, provide a '
402 402 'valid clone http(s)/svn+http(s) url')
403 403 }
404 404
405 405 def validate_python(self, value, state):
406 406 repo_type = value.get('repo_type')
407 407 url = value.get('clone_uri')
408 408
409 409 if not url:
410 410 pass
411 411 else:
412 412 try:
413 url_handler(repo_type, url, make_ui('db'))
413 url_handler(repo_type, url, make_ui('db', clear_session=False))
414 414 except Exception:
415 415 log.exception('Url validation failed')
416 416 msg = M(self, 'clone_uri')
417 417 raise formencode.Invalid(msg, value, state,
418 418 error_dict=dict(clone_uri=msg)
419 419 )
420 420 return _validator
421 421
422 422
423 423 def ValidForkType(old_data={}):
424 424 class _validator(formencode.validators.FancyValidator):
425 425 messages = {
426 426 'invalid_fork_type': _(u'Fork have to be the same type as parent')
427 427 }
428 428
429 429 def validate_python(self, value, state):
430 430 if old_data['repo_type'] != value:
431 431 msg = M(self, 'invalid_fork_type', state)
432 432 raise formencode.Invalid(msg, value, state,
433 433 error_dict=dict(repo_type=msg)
434 434 )
435 435 return _validator
436 436
437 437
438 438 def ValidPerms(type_='repo'):
439 439 if type_ == 'group':
440 440 EMPTY_PERM = 'group.none'
441 441 elif type_ == 'repo':
442 442 EMPTY_PERM = 'repository.none'
443 443
444 444 class _validator(formencode.validators.FancyValidator):
445 445 messages = {
446 446 'perm_new_member_name':
447 447 _(u'This username or users group name is not valid')
448 448 }
449 449
450 450 def to_python(self, value, state):
451 451 perms_update = []
452 452 perms_new = []
453 453 # build a list of permission to update and new permission to create
454 454 for k, v in value.items():
455 455 # means new added member to permissions
456 456 if k.startswith('perm_new_member'):
457 457 new_perm = value.get('perm_new_member', False)
458 458 new_member = value.get('perm_new_member_name', False)
459 459 new_type = value.get('perm_new_member_type')
460 460
461 461 if new_member and new_perm:
462 462 if (new_member, new_perm, new_type) not in perms_new:
463 463 perms_new.append((new_member, new_perm, new_type))
464 464 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
465 465 member = k[7:]
466 466 t = {'u': 'user',
467 467 'g': 'users_group'
468 468 }[k[0]]
469 469 if member == 'default':
470 470 if value.get('private'):
471 471 # set none for default when updating to
472 472 # private repo
473 473 v = EMPTY_PERM
474 474 perms_update.append((member, v, t))
475 475
476 476 value['perms_updates'] = perms_update
477 477 value['perms_new'] = perms_new
478 478
479 479 # update permissions
480 480 for k, v, t in perms_new:
481 481 try:
482 482 if t is 'user':
483 483 self.user_db = User.query()\
484 484 .filter(User.active == True)\
485 485 .filter(User.username == k).one()
486 486 if t is 'users_group':
487 487 self.user_db = UsersGroup.query()\
488 488 .filter(UsersGroup.users_group_active == True)\
489 489 .filter(UsersGroup.users_group_name == k).one()
490 490
491 491 except Exception:
492 492 log.exception('Updated permission failed')
493 493 msg = M(self, 'perm_new_member_type', state)
494 494 raise formencode.Invalid(msg, value, state,
495 495 error_dict=dict(perm_new_member_name=msg)
496 496 )
497 497 return value
498 498 return _validator
499 499
500 500
501 501 def ValidSettings():
502 502 class _validator(formencode.validators.FancyValidator):
503 503 def _to_python(self, value, state):
504 504 # settings form can't edit user
505 505 if 'user' in value:
506 506 del value['user']
507 507 return value
508 508
509 509 def validate_python(self, value, state):
510 510 pass
511 511 return _validator
512 512
513 513
514 514 def ValidPath():
515 515 class _validator(formencode.validators.FancyValidator):
516 516 messages = {
517 517 'invalid_path': _(u'This is not a valid path')
518 518 }
519 519
520 520 def validate_python(self, value, state):
521 521 if not os.path.isdir(value):
522 522 msg = M(self, 'invalid_path', state)
523 523 raise formencode.Invalid(msg, value, state,
524 524 error_dict=dict(paths_root_path=msg)
525 525 )
526 526 return _validator
527 527
528 528
529 529 def UniqSystemEmail(old_data={}):
530 530 class _validator(formencode.validators.FancyValidator):
531 531 messages = {
532 532 'email_taken': _(u'This e-mail address is already taken')
533 533 }
534 534
535 535 def _to_python(self, value, state):
536 536 return value.lower()
537 537
538 538 def validate_python(self, value, state):
539 539 if (old_data.get('email') or '').lower() != value:
540 540 user = User.get_by_email(value, case_insensitive=True)
541 541 if user:
542 542 msg = M(self, 'email_taken', state)
543 543 raise formencode.Invalid(msg, value, state,
544 544 error_dict=dict(email=msg)
545 545 )
546 546 return _validator
547 547
548 548
549 549 def ValidSystemEmail():
550 550 class _validator(formencode.validators.FancyValidator):
551 551 messages = {
552 552 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
553 553 }
554 554
555 555 def _to_python(self, value, state):
556 556 return value.lower()
557 557
558 558 def validate_python(self, value, state):
559 559 user = User.get_by_email(value, case_insensitive=True)
560 560 if user is None:
561 561 msg = M(self, 'non_existing_email', state, email=value)
562 562 raise formencode.Invalid(msg, value, state,
563 563 error_dict=dict(email=msg)
564 564 )
565 565
566 566 return _validator
567 567
568 568
569 569 def LdapLibValidator():
570 570 class _validator(formencode.validators.FancyValidator):
571 571 messages = {
572 572
573 573 }
574 574
575 575 def validate_python(self, value, state):
576 576 try:
577 577 import ldap
578 578 ldap # pyflakes silence !
579 579 except ImportError:
580 580 raise LdapImportError()
581 581
582 582 return _validator
583 583
584 584
585 585 def AttrLoginValidator():
586 586 class _validator(formencode.validators.FancyValidator):
587 587 messages = {
588 588 'invalid_cn':
589 589 _(u'The LDAP Login attribute of the CN must be specified - '
590 590 'this is the name of the attribute that is equivalent '
591 591 'to "username"')
592 592 }
593 593
594 594 def validate_python(self, value, state):
595 595 if not value or not isinstance(value, (str, unicode)):
596 596 msg = M(self, 'invalid_cn', state)
597 597 raise formencode.Invalid(msg, value, state,
598 598 error_dict=dict(ldap_attr_login=msg)
599 599 )
600 600
601 601 return _validator
General Comments 0
You need to be logged in to leave comments. Login now