##// END OF EJS Templates
authn: Adapt authentication plugins validator to allow legacy plugins....
johbo -
r136:1a61264b default
parent child Browse files
Show More
@@ -1,1077 +1,1120 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 from rhodecode.authentication import legacy_plugin_prefix
41 from rhodecode.authentication import (
42 legacy_plugin_prefix, _import_legacy_plugin)
43 from rhodecode.authentication.base import loadplugin
42 44 from rhodecode.config.routing import ADMIN_PREFIX
43 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
44 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
45 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
46 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
47 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
48 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
49 51 from rhodecode.model.db import (
50 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
51 53 from rhodecode.model.settings import VcsSettingsModel
52 54
53 55 # silence warnings and pylint
54 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
55 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
56 58
57 59 log = logging.getLogger(__name__)
58 60
59 61
60 62 class _Missing(object):
61 63 pass
62 64
63 65 Missing = _Missing()
64 66
65 67
66 68 class StateObj(object):
67 69 """
68 70 this is needed to translate the messages using _() in validators
69 71 """
70 72 _ = staticmethod(_)
71 73
72 74
73 75 def M(self, key, state=None, **kwargs):
74 76 """
75 77 returns string from self.message based on given key,
76 78 passed kw params are used to substitute %(named)s params inside
77 79 translated strings
78 80
79 81 :param msg:
80 82 :param state:
81 83 """
82 84 if state is None:
83 85 state = StateObj()
84 86 else:
85 87 state._ = staticmethod(_)
86 88 # inject validator into state object
87 89 return self.message(key, state, **kwargs)
88 90
89 91
90 92 def UniqueList(convert=None):
91 93 class _UniqueList(formencode.FancyValidator):
92 94 """
93 95 Unique List !
94 96 """
95 97 messages = {
96 98 'empty': _(u'Value cannot be an empty list'),
97 99 'missing_value': _(u'Value cannot be an empty list'),
98 100 }
99 101
100 102 def _to_python(self, value, state):
101 103 ret_val = []
102 104
103 105 def make_unique(value):
104 106 seen = []
105 107 return [c for c in value if not (c in seen or seen.append(c))]
106 108
107 109 if isinstance(value, list):
108 110 ret_val = make_unique(value)
109 111 elif isinstance(value, set):
110 112 ret_val = make_unique(list(value))
111 113 elif isinstance(value, tuple):
112 114 ret_val = make_unique(list(value))
113 115 elif value is None:
114 116 ret_val = []
115 117 else:
116 118 ret_val = [value]
117 119
118 120 if convert:
119 121 ret_val = map(convert, ret_val)
120 122 return ret_val
121 123
122 124 def empty_value(self, value):
123 125 return []
124 126
125 127 return _UniqueList
126 128
127 129
128 130 def UniqueListFromString():
129 131 class _UniqueListFromString(UniqueList()):
130 132 def _to_python(self, value, state):
131 133 if isinstance(value, basestring):
132 134 value = aslist(value, ',')
133 135 return super(_UniqueListFromString, self)._to_python(value, state)
134 136 return _UniqueListFromString
135 137
136 138
137 139 def ValidSvnPattern(section, repo_name=None):
138 140 class _validator(formencode.validators.FancyValidator):
139 141 messages = {
140 142 'pattern_exists': _(u'Pattern already exists'),
141 143 }
142 144
143 145 def validate_python(self, value, state):
144 146 if not value:
145 147 return
146 148 model = VcsSettingsModel(repo=repo_name)
147 149 ui_settings = model.get_svn_patterns(section=section)
148 150 for entry in ui_settings:
149 151 if value == entry.value:
150 152 msg = M(self, 'pattern_exists', state)
151 153 raise formencode.Invalid(msg, value, state)
152 154 return _validator
153 155
154 156
155 157 def ValidUsername(edit=False, old_data={}):
156 158 class _validator(formencode.validators.FancyValidator):
157 159 messages = {
158 160 'username_exists': _(u'Username "%(username)s" already exists'),
159 161 'system_invalid_username':
160 162 _(u'Username "%(username)s" is forbidden'),
161 163 'invalid_username':
162 164 _(u'Username may only contain alphanumeric characters '
163 165 u'underscores, periods or dashes and must begin with '
164 166 u'alphanumeric character or underscore')
165 167 }
166 168
167 169 def validate_python(self, value, state):
168 170 if value in ['default', 'new_user']:
169 171 msg = M(self, 'system_invalid_username', state, username=value)
170 172 raise formencode.Invalid(msg, value, state)
171 173 # check if user is unique
172 174 old_un = None
173 175 if edit:
174 176 old_un = User.get(old_data.get('user_id')).username
175 177
176 178 if old_un != value or not edit:
177 179 if User.get_by_username(value, case_insensitive=True):
178 180 msg = M(self, 'username_exists', state, username=value)
179 181 raise formencode.Invalid(msg, value, state)
180 182
181 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
182 184 is None):
183 185 msg = M(self, 'invalid_username', state)
184 186 raise formencode.Invalid(msg, value, state)
185 187 return _validator
186 188
187 189
188 190 def ValidRegex(msg=None):
189 191 class _validator(formencode.validators.Regex):
190 192 messages = {'invalid': msg or _(u'The input is not valid')}
191 193 return _validator
192 194
193 195
194 196 def ValidRepoUser():
195 197 class _validator(formencode.validators.FancyValidator):
196 198 messages = {
197 199 'invalid_username': _(u'Username %(username)s is not valid')
198 200 }
199 201
200 202 def validate_python(self, value, state):
201 203 try:
202 204 User.query().filter(User.active == true())\
203 205 .filter(User.username == value).one()
204 206 except Exception:
205 207 msg = M(self, 'invalid_username', state, username=value)
206 208 raise formencode.Invalid(
207 209 msg, value, state, error_dict={'username': msg}
208 210 )
209 211
210 212 return _validator
211 213
212 214
213 215 def ValidUserGroup(edit=False, old_data={}):
214 216 class _validator(formencode.validators.FancyValidator):
215 217 messages = {
216 218 'invalid_group': _(u'Invalid user group name'),
217 219 'group_exist': _(u'User group "%(usergroup)s" already exists'),
218 220 'invalid_usergroup_name':
219 221 _(u'user group name may only contain alphanumeric '
220 222 u'characters underscores, periods or dashes and must begin '
221 223 u'with alphanumeric character')
222 224 }
223 225
224 226 def validate_python(self, value, state):
225 227 if value in ['default']:
226 228 msg = M(self, 'invalid_group', state)
227 229 raise formencode.Invalid(
228 230 msg, value, state, error_dict={'users_group_name': msg}
229 231 )
230 232 # check if group is unique
231 233 old_ugname = None
232 234 if edit:
233 235 old_id = old_data.get('users_group_id')
234 236 old_ugname = UserGroup.get(old_id).users_group_name
235 237
236 238 if old_ugname != value or not edit:
237 239 is_existing_group = UserGroup.get_by_group_name(
238 240 value, case_insensitive=True)
239 241 if is_existing_group:
240 242 msg = M(self, 'group_exist', state, usergroup=value)
241 243 raise formencode.Invalid(
242 244 msg, value, state, error_dict={'users_group_name': msg}
243 245 )
244 246
245 247 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
246 248 msg = M(self, 'invalid_usergroup_name', state)
247 249 raise formencode.Invalid(
248 250 msg, value, state, error_dict={'users_group_name': msg}
249 251 )
250 252
251 253 return _validator
252 254
253 255
254 256 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
255 257 class _validator(formencode.validators.FancyValidator):
256 258 messages = {
257 259 'group_parent_id': _(u'Cannot assign this group as parent'),
258 260 'group_exists': _(u'Group "%(group_name)s" already exists'),
259 261 'repo_exists': _(u'Repository with name "%(group_name)s" '
260 262 u'already exists'),
261 263 'permission_denied': _(u"no permission to store repository group"
262 264 u"in this location"),
263 265 'permission_denied_root': _(
264 266 u"no permission to store repository group "
265 267 u"in root location")
266 268 }
267 269
268 270 def _to_python(self, value, state):
269 271 group_name = repo_name_slug(value.get('group_name', ''))
270 272 group_parent_id = safe_int(value.get('group_parent_id'))
271 273 gr = RepoGroup.get(group_parent_id)
272 274 if gr:
273 275 parent_group_path = gr.full_path
274 276 # value needs to be aware of group name in order to check
275 277 # db key This is an actual just the name to store in the
276 278 # database
277 279 group_name_full = (
278 280 parent_group_path + RepoGroup.url_sep() + group_name)
279 281 else:
280 282 group_name_full = group_name
281 283
282 284 value['group_name'] = group_name
283 285 value['group_name_full'] = group_name_full
284 286 value['group_parent_id'] = group_parent_id
285 287 return value
286 288
287 289 def validate_python(self, value, state):
288 290
289 291 old_group_name = None
290 292 group_name = value.get('group_name')
291 293 group_name_full = value.get('group_name_full')
292 294 group_parent_id = safe_int(value.get('group_parent_id'))
293 295 if group_parent_id == -1:
294 296 group_parent_id = None
295 297
296 298 group_obj = RepoGroup.get(old_data.get('group_id'))
297 299 parent_group_changed = False
298 300 if edit:
299 301 old_group_name = group_obj.group_name
300 302 old_group_parent_id = group_obj.group_parent_id
301 303
302 304 if group_parent_id != old_group_parent_id:
303 305 parent_group_changed = True
304 306
305 307 # TODO: mikhail: the following if statement is not reached
306 308 # since group_parent_id's OneOf validation fails before.
307 309 # Can be removed.
308 310
309 311 # check against setting a parent of self
310 312 parent_of_self = (
311 313 old_data['group_id'] == group_parent_id
312 314 if group_parent_id else False
313 315 )
314 316 if parent_of_self:
315 317 msg = M(self, 'group_parent_id', state)
316 318 raise formencode.Invalid(
317 319 msg, value, state, error_dict={'group_parent_id': msg}
318 320 )
319 321
320 322 # group we're moving current group inside
321 323 child_group = None
322 324 if group_parent_id:
323 325 child_group = RepoGroup.query().filter(
324 326 RepoGroup.group_id == group_parent_id).scalar()
325 327
326 328 # do a special check that we cannot move a group to one of
327 329 # it's children
328 330 if edit and child_group:
329 331 parents = [x.group_id for x in child_group.parents]
330 332 move_to_children = old_data['group_id'] in parents
331 333 if move_to_children:
332 334 msg = M(self, 'group_parent_id', state)
333 335 raise formencode.Invalid(
334 336 msg, value, state, error_dict={'group_parent_id': msg})
335 337
336 338 # Check if we have permission to store in the parent.
337 339 # Only check if the parent group changed.
338 340 if parent_group_changed:
339 341 if child_group is None:
340 342 if not can_create_in_root:
341 343 msg = M(self, 'permission_denied_root', state)
342 344 raise formencode.Invalid(
343 345 msg, value, state,
344 346 error_dict={'group_parent_id': msg})
345 347 else:
346 348 valid = HasRepoGroupPermissionAny('group.admin')
347 349 forbidden = not valid(
348 350 child_group.group_name, 'can create group validator')
349 351 if forbidden:
350 352 msg = M(self, 'permission_denied', state)
351 353 raise formencode.Invalid(
352 354 msg, value, state,
353 355 error_dict={'group_parent_id': msg})
354 356
355 357 # if we change the name or it's new group, check for existing names
356 358 # or repositories with the same name
357 359 if old_group_name != group_name_full or not edit:
358 360 # check group
359 361 gr = RepoGroup.get_by_group_name(group_name_full)
360 362 if gr:
361 363 msg = M(self, 'group_exists', state, group_name=group_name)
362 364 raise formencode.Invalid(
363 365 msg, value, state, error_dict={'group_name': msg})
364 366
365 367 # check for same repo
366 368 repo = Repository.get_by_repo_name(group_name_full)
367 369 if repo:
368 370 msg = M(self, 'repo_exists', state, group_name=group_name)
369 371 raise formencode.Invalid(
370 372 msg, value, state, error_dict={'group_name': msg})
371 373
372 374 return _validator
373 375
374 376
375 377 def ValidPassword():
376 378 class _validator(formencode.validators.FancyValidator):
377 379 messages = {
378 380 'invalid_password':
379 381 _(u'Invalid characters (non-ascii) in password')
380 382 }
381 383
382 384 def validate_python(self, value, state):
383 385 try:
384 386 (value or '').decode('ascii')
385 387 except UnicodeError:
386 388 msg = M(self, 'invalid_password', state)
387 389 raise formencode.Invalid(msg, value, state,)
388 390 return _validator
389 391
390 392
391 393 def ValidOldPassword(username):
392 394 class _validator(formencode.validators.FancyValidator):
393 395 messages = {
394 396 'invalid_password': _(u'Invalid old password')
395 397 }
396 398
397 399 def validate_python(self, value, state):
398 400 from rhodecode.authentication.base import authenticate, HTTP_TYPE
399 401 if not authenticate(username, value, '', HTTP_TYPE):
400 402 msg = M(self, 'invalid_password', state)
401 403 raise formencode.Invalid(
402 404 msg, value, state, error_dict={'current_password': msg}
403 405 )
404 406 return _validator
405 407
406 408
407 409 def ValidPasswordsMatch(
408 410 passwd='new_password', passwd_confirmation='password_confirmation'):
409 411 class _validator(formencode.validators.FancyValidator):
410 412 messages = {
411 413 'password_mismatch': _(u'Passwords do not match'),
412 414 }
413 415
414 416 def validate_python(self, value, state):
415 417
416 418 pass_val = value.get('password') or value.get(passwd)
417 419 if pass_val != value[passwd_confirmation]:
418 420 msg = M(self, 'password_mismatch', state)
419 421 raise formencode.Invalid(
420 422 msg, value, state,
421 423 error_dict={passwd: msg, passwd_confirmation: msg}
422 424 )
423 425 return _validator
424 426
425 427
426 428 def ValidAuth():
427 429 class _validator(formencode.validators.FancyValidator):
428 430 messages = {
429 431 'invalid_password': _(u'invalid password'),
430 432 'invalid_username': _(u'invalid user name'),
431 433 'disabled_account': _(u'Your account is disabled')
432 434 }
433 435
434 436 def validate_python(self, value, state):
435 437 from rhodecode.authentication.base import authenticate, HTTP_TYPE
436 438
437 439 password = value['password']
438 440 username = value['username']
439 441
440 442 if not authenticate(username, password, '', HTTP_TYPE,
441 443 skip_missing=True):
442 444 user = User.get_by_username(username)
443 445 if user and not user.active:
444 446 log.warning('user %s is disabled', username)
445 447 msg = M(self, 'disabled_account', state)
446 448 raise formencode.Invalid(
447 449 msg, value, state, error_dict={'username': msg}
448 450 )
449 451 else:
450 452 log.warning('user `%s` failed to authenticate', username)
451 453 msg = M(self, 'invalid_username', state)
452 454 msg2 = M(self, 'invalid_password', state)
453 455 raise formencode.Invalid(
454 456 msg, value, state,
455 457 error_dict={'username': msg, 'password': msg2}
456 458 )
457 459 return _validator
458 460
459 461
460 462 def ValidAuthToken():
461 463 class _validator(formencode.validators.FancyValidator):
462 464 messages = {
463 465 'invalid_token': _(u'Token mismatch')
464 466 }
465 467
466 468 def validate_python(self, value, state):
467 469 if value != authentication_token():
468 470 msg = M(self, 'invalid_token', state)
469 471 raise formencode.Invalid(msg, value, state)
470 472 return _validator
471 473
472 474
473 475 def ValidRepoName(edit=False, old_data={}):
474 476 class _validator(formencode.validators.FancyValidator):
475 477 messages = {
476 478 'invalid_repo_name':
477 479 _(u'Repository name %(repo)s is disallowed'),
478 480 # top level
479 481 'repository_exists': _(u'Repository with name %(repo)s '
480 482 u'already exists'),
481 483 'group_exists': _(u'Repository group with name "%(repo)s" '
482 484 u'already exists'),
483 485 # inside a group
484 486 'repository_in_group_exists': _(u'Repository with name %(repo)s '
485 487 u'exists in group "%(group)s"'),
486 488 'group_in_group_exists': _(
487 489 u'Repository group with name "%(repo)s" '
488 490 u'exists in group "%(group)s"'),
489 491 }
490 492
491 493 def _to_python(self, value, state):
492 494 repo_name = repo_name_slug(value.get('repo_name', ''))
493 495 repo_group = value.get('repo_group')
494 496 if repo_group:
495 497 gr = RepoGroup.get(repo_group)
496 498 group_path = gr.full_path
497 499 group_name = gr.group_name
498 500 # value needs to be aware of group name in order to check
499 501 # db key This is an actual just the name to store in the
500 502 # database
501 503 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
502 504 else:
503 505 group_name = group_path = ''
504 506 repo_name_full = repo_name
505 507
506 508 value['repo_name'] = repo_name
507 509 value['repo_name_full'] = repo_name_full
508 510 value['group_path'] = group_path
509 511 value['group_name'] = group_name
510 512 return value
511 513
512 514 def validate_python(self, value, state):
513 515
514 516 repo_name = value.get('repo_name')
515 517 repo_name_full = value.get('repo_name_full')
516 518 group_path = value.get('group_path')
517 519 group_name = value.get('group_name')
518 520
519 521 if repo_name in [ADMIN_PREFIX, '']:
520 522 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
521 523 raise formencode.Invalid(
522 524 msg, value, state, error_dict={'repo_name': msg})
523 525
524 526 rename = old_data.get('repo_name') != repo_name_full
525 527 create = not edit
526 528 if rename or create:
527 529
528 530 if group_path:
529 531 if Repository.get_by_repo_name(repo_name_full):
530 532 msg = M(self, 'repository_in_group_exists', state,
531 533 repo=repo_name, group=group_name)
532 534 raise formencode.Invalid(
533 535 msg, value, state, error_dict={'repo_name': msg})
534 536 if RepoGroup.get_by_group_name(repo_name_full):
535 537 msg = M(self, 'group_in_group_exists', state,
536 538 repo=repo_name, group=group_name)
537 539 raise formencode.Invalid(
538 540 msg, value, state, error_dict={'repo_name': msg})
539 541 else:
540 542 if RepoGroup.get_by_group_name(repo_name_full):
541 543 msg = M(self, 'group_exists', state, repo=repo_name)
542 544 raise formencode.Invalid(
543 545 msg, value, state, error_dict={'repo_name': msg})
544 546
545 547 if Repository.get_by_repo_name(repo_name_full):
546 548 msg = M(
547 549 self, 'repository_exists', state, repo=repo_name)
548 550 raise formencode.Invalid(
549 551 msg, value, state, error_dict={'repo_name': msg})
550 552 return value
551 553 return _validator
552 554
553 555
554 556 def ValidForkName(*args, **kwargs):
555 557 return ValidRepoName(*args, **kwargs)
556 558
557 559
558 560 def SlugifyName():
559 561 class _validator(formencode.validators.FancyValidator):
560 562
561 563 def _to_python(self, value, state):
562 564 return repo_name_slug(value)
563 565
564 566 def validate_python(self, value, state):
565 567 pass
566 568
567 569 return _validator
568 570
569 571
570 572 def ValidCloneUri():
571 573 class InvalidCloneUrl(Exception):
572 574 allowed_prefixes = ()
573 575
574 576 def url_handler(repo_type, url):
575 577 config = make_db_config(clear_session=False)
576 578 if repo_type == 'hg':
577 579 allowed_prefixes = ('http', 'svn+http', 'git+http')
578 580
579 581 if 'http' in url[:4]:
580 582 # initially check if it's at least the proper URL
581 583 # or does it pass basic auth
582 584 MercurialRepository.check_url(url, config)
583 585 elif 'svn+http' in url[:8]: # svn->hg import
584 586 SubversionRepository.check_url(url, config)
585 587 elif 'git+http' in url[:8]: # git->hg import
586 588 raise NotImplementedError()
587 589 else:
588 590 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
589 591 'Allowed url must start with one of %s'
590 592 % (url, ','.join(allowed_prefixes)))
591 593 exc.allowed_prefixes = allowed_prefixes
592 594 raise exc
593 595
594 596 elif repo_type == 'git':
595 597 allowed_prefixes = ('http', 'svn+http', 'hg+http')
596 598 if 'http' in url[:4]:
597 599 # initially check if it's at least the proper URL
598 600 # or does it pass basic auth
599 601 GitRepository.check_url(url, config)
600 602 elif 'svn+http' in url[:8]: # svn->git import
601 603 raise NotImplementedError()
602 604 elif 'hg+http' in url[:8]: # hg->git import
603 605 raise NotImplementedError()
604 606 else:
605 607 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
606 608 'Allowed url must start with one of %s'
607 609 % (url, ','.join(allowed_prefixes)))
608 610 exc.allowed_prefixes = allowed_prefixes
609 611 raise exc
610 612
611 613 class _validator(formencode.validators.FancyValidator):
612 614 messages = {
613 615 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
614 616 'invalid_clone_uri': _(
615 617 u'Invalid clone url, provide a valid clone '
616 618 u'url starting with one of %(allowed_prefixes)s')
617 619 }
618 620
619 621 def validate_python(self, value, state):
620 622 repo_type = value.get('repo_type')
621 623 url = value.get('clone_uri')
622 624
623 625 if url:
624 626 try:
625 627 url_handler(repo_type, url)
626 628 except InvalidCloneUrl as e:
627 629 log.warning(e)
628 630 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
629 631 allowed_prefixes=','.join(e.allowed_prefixes))
630 632 raise formencode.Invalid(msg, value, state,
631 633 error_dict={'clone_uri': msg})
632 634 except Exception:
633 635 log.exception('Url validation failed')
634 636 msg = M(self, 'clone_uri', rtype=repo_type)
635 637 raise formencode.Invalid(msg, value, state,
636 638 error_dict={'clone_uri': msg})
637 639 return _validator
638 640
639 641
640 642 def ValidForkType(old_data={}):
641 643 class _validator(formencode.validators.FancyValidator):
642 644 messages = {
643 645 'invalid_fork_type': _(u'Fork have to be the same type as parent')
644 646 }
645 647
646 648 def validate_python(self, value, state):
647 649 if old_data['repo_type'] != value:
648 650 msg = M(self, 'invalid_fork_type', state)
649 651 raise formencode.Invalid(
650 652 msg, value, state, error_dict={'repo_type': msg}
651 653 )
652 654 return _validator
653 655
654 656
655 657 def CanWriteGroup(old_data=None):
656 658 class _validator(formencode.validators.FancyValidator):
657 659 messages = {
658 660 'permission_denied': _(
659 661 u"You do not have the permission "
660 662 u"to create repositories in this group."),
661 663 'permission_denied_root': _(
662 664 u"You do not have the permission to store repositories in "
663 665 u"the root location.")
664 666 }
665 667
666 668 def _to_python(self, value, state):
667 669 # root location
668 670 if value in [-1, "-1"]:
669 671 return None
670 672 return value
671 673
672 674 def validate_python(self, value, state):
673 675 gr = RepoGroup.get(value)
674 676 gr_name = gr.group_name if gr else None # None means ROOT location
675 677 # create repositories with write permission on group is set to true
676 678 create_on_write = HasPermissionAny(
677 679 'hg.create.write_on_repogroup.true')()
678 680 group_admin = HasRepoGroupPermissionAny('group.admin')(
679 681 gr_name, 'can write into group validator')
680 682 group_write = HasRepoGroupPermissionAny('group.write')(
681 683 gr_name, 'can write into group validator')
682 684 forbidden = not (group_admin or (group_write and create_on_write))
683 685 can_create_repos = HasPermissionAny(
684 686 'hg.admin', 'hg.create.repository')
685 687 gid = (old_data['repo_group'].get('group_id')
686 688 if (old_data and 'repo_group' in old_data) else None)
687 689 value_changed = gid != safe_int(value)
688 690 new = not old_data
689 691 # do check if we changed the value, there's a case that someone got
690 692 # revoked write permissions to a repository, he still created, we
691 693 # don't need to check permission if he didn't change the value of
692 694 # groups in form box
693 695 if value_changed or new:
694 696 # parent group need to be existing
695 697 if gr and forbidden:
696 698 msg = M(self, 'permission_denied', state)
697 699 raise formencode.Invalid(
698 700 msg, value, state, error_dict={'repo_type': msg}
699 701 )
700 702 # check if we can write to root location !
701 703 elif gr is None and not can_create_repos():
702 704 msg = M(self, 'permission_denied_root', state)
703 705 raise formencode.Invalid(
704 706 msg, value, state, error_dict={'repo_type': msg}
705 707 )
706 708
707 709 return _validator
708 710
709 711
710 712 def ValidPerms(type_='repo'):
711 713 if type_ == 'repo_group':
712 714 EMPTY_PERM = 'group.none'
713 715 elif type_ == 'repo':
714 716 EMPTY_PERM = 'repository.none'
715 717 elif type_ == 'user_group':
716 718 EMPTY_PERM = 'usergroup.none'
717 719
718 720 class _validator(formencode.validators.FancyValidator):
719 721 messages = {
720 722 'perm_new_member_name':
721 723 _(u'This username or user group name is not valid')
722 724 }
723 725
724 726 def _to_python(self, value, state):
725 727 perm_updates = OrderedSet()
726 728 perm_additions = OrderedSet()
727 729 perm_deletions = OrderedSet()
728 730 # build a list of permission to update/delete and new permission
729 731
730 732 # Read the perm_new_member/perm_del_member attributes and group
731 733 # them by they IDs
732 734 new_perms_group = defaultdict(dict)
733 735 del_perms_group = defaultdict(dict)
734 736 for k, v in value.copy().iteritems():
735 737 if k.startswith('perm_del_member'):
736 738 # delete from org storage so we don't process that later
737 739 del value[k]
738 740 # part is `id`, `type`
739 741 _type, part = k.split('perm_del_member_')
740 742 args = part.split('_')
741 743 if len(args) == 2:
742 744 _key, pos = args
743 745 del_perms_group[pos][_key] = v
744 746 if k.startswith('perm_new_member'):
745 747 # delete from org storage so we don't process that later
746 748 del value[k]
747 749 # part is `id`, `type`, `perm`
748 750 _type, part = k.split('perm_new_member_')
749 751 args = part.split('_')
750 752 if len(args) == 2:
751 753 _key, pos = args
752 754 new_perms_group[pos][_key] = v
753 755
754 756 # store the deletes
755 757 for k in sorted(del_perms_group.keys()):
756 758 perm_dict = del_perms_group[k]
757 759 del_member = perm_dict.get('id')
758 760 del_type = perm_dict.get('type')
759 761 if del_member and del_type:
760 762 perm_deletions.add((del_member, None, del_type))
761 763
762 764 # store additions in order of how they were added in web form
763 765 for k in sorted(new_perms_group.keys()):
764 766 perm_dict = new_perms_group[k]
765 767 new_member = perm_dict.get('id')
766 768 new_type = perm_dict.get('type')
767 769 new_perm = perm_dict.get('perm')
768 770 if new_member and new_perm and new_type:
769 771 perm_additions.add((new_member, new_perm, new_type))
770 772
771 773 # get updates of permissions
772 774 # (read the existing radio button states)
773 775 for k, update_value in value.iteritems():
774 776 if k.startswith('u_perm_') or k.startswith('g_perm_'):
775 777 member = k[7:]
776 778 update_type = {'u': 'user',
777 779 'g': 'users_group'}[k[0]]
778 780 if member == User.DEFAULT_USER:
779 781 if str2bool(value.get('repo_private')):
780 782 # set none for default when updating to
781 783 # private repo protects agains form manipulation
782 784 update_value = EMPTY_PERM
783 785 perm_updates.add((member, update_value, update_type))
784 786 # check the deletes
785 787
786 788 value['perm_additions'] = list(perm_additions)
787 789 value['perm_updates'] = list(perm_updates)
788 790 value['perm_deletions'] = list(perm_deletions)
789 791
790 792 # validate users they exist and they are active !
791 793 for member_id, _perm, member_type in perm_additions:
792 794 try:
793 795 if member_type == 'user':
794 796 self.user_db = User.query()\
795 797 .filter(User.active == true())\
796 798 .filter(User.user_id == member_id).one()
797 799 if member_type == 'users_group':
798 800 self.user_db = UserGroup.query()\
799 801 .filter(UserGroup.users_group_active == true())\
800 802 .filter(UserGroup.users_group_id == member_id)\
801 803 .one()
802 804
803 805 except Exception:
804 806 log.exception('Updated permission failed: org_exc:')
805 807 msg = M(self, 'perm_new_member_type', state)
806 808 raise formencode.Invalid(
807 809 msg, value, state, error_dict={
808 810 'perm_new_member_name': msg}
809 811 )
810 812 return value
811 813 return _validator
812 814
813 815
814 816 def ValidSettings():
815 817 class _validator(formencode.validators.FancyValidator):
816 818 def _to_python(self, value, state):
817 819 # settings form for users that are not admin
818 820 # can't edit certain parameters, it's extra backup if they mangle
819 821 # with forms
820 822
821 823 forbidden_params = [
822 824 'user', 'repo_type', 'repo_enable_locking',
823 825 'repo_enable_downloads', 'repo_enable_statistics'
824 826 ]
825 827
826 828 for param in forbidden_params:
827 829 if param in value:
828 830 del value[param]
829 831 return value
830 832
831 833 def validate_python(self, value, state):
832 834 pass
833 835 return _validator
834 836
835 837
836 838 def ValidPath():
837 839 class _validator(formencode.validators.FancyValidator):
838 840 messages = {
839 841 'invalid_path': _(u'This is not a valid path')
840 842 }
841 843
842 844 def validate_python(self, value, state):
843 845 if not os.path.isdir(value):
844 846 msg = M(self, 'invalid_path', state)
845 847 raise formencode.Invalid(
846 848 msg, value, state, error_dict={'paths_root_path': msg}
847 849 )
848 850 return _validator
849 851
850 852
851 853 def UniqSystemEmail(old_data={}):
852 854 class _validator(formencode.validators.FancyValidator):
853 855 messages = {
854 856 'email_taken': _(u'This e-mail address is already taken')
855 857 }
856 858
857 859 def _to_python(self, value, state):
858 860 return value.lower()
859 861
860 862 def validate_python(self, value, state):
861 863 if (old_data.get('email') or '').lower() != value:
862 864 user = User.get_by_email(value, case_insensitive=True)
863 865 if user:
864 866 msg = M(self, 'email_taken', state)
865 867 raise formencode.Invalid(
866 868 msg, value, state, error_dict={'email': msg}
867 869 )
868 870 return _validator
869 871
870 872
871 873 def ValidSystemEmail():
872 874 class _validator(formencode.validators.FancyValidator):
873 875 messages = {
874 876 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
875 877 }
876 878
877 879 def _to_python(self, value, state):
878 880 return value.lower()
879 881
880 882 def validate_python(self, value, state):
881 883 user = User.get_by_email(value, case_insensitive=True)
882 884 if user is None:
883 885 msg = M(self, 'non_existing_email', state, email=value)
884 886 raise formencode.Invalid(
885 887 msg, value, state, error_dict={'email': msg}
886 888 )
887 889
888 890 return _validator
889 891
890 892
891 893 def NotReviewedRevisions(repo_id):
892 894 class _validator(formencode.validators.FancyValidator):
893 895 messages = {
894 896 'rev_already_reviewed':
895 897 _(u'Revisions %(revs)s are already part of pull request '
896 898 u'or have set status'),
897 899 }
898 900
899 901 def validate_python(self, value, state):
900 902 # check revisions if they are not reviewed, or a part of another
901 903 # pull request
902 904 statuses = ChangesetStatus.query()\
903 905 .filter(ChangesetStatus.revision.in_(value))\
904 906 .filter(ChangesetStatus.repo_id == repo_id)\
905 907 .all()
906 908
907 909 errors = []
908 910 for status in statuses:
909 911 if status.pull_request_id:
910 912 errors.append(['pull_req', status.revision[:12]])
911 913 elif status.status:
912 914 errors.append(['status', status.revision[:12]])
913 915
914 916 if errors:
915 917 revs = ','.join([x[1] for x in errors])
916 918 msg = M(self, 'rev_already_reviewed', state, revs=revs)
917 919 raise formencode.Invalid(
918 920 msg, value, state, error_dict={'revisions': revs})
919 921
920 922 return _validator
921 923
922 924
923 925 def ValidIp():
924 926 class _validator(CIDR):
925 927 messages = {
926 928 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
927 929 'illegalBits': _(
928 930 u'The network size (bits) must be within the range '
929 931 u'of 0-32 (not %(bits)r)'),
930 932 }
931 933
932 934 # we ovveride the default to_python() call
933 935 def to_python(self, value, state):
934 936 v = super(_validator, self).to_python(value, state)
935 937 v = v.strip()
936 938 net = ipaddress.ip_network(address=v, strict=False)
937 939 return str(net)
938 940
939 941 def validate_python(self, value, state):
940 942 try:
941 943 addr = value.strip()
942 944 # this raises an ValueError if address is not IpV4 or IpV6
943 945 ipaddress.ip_network(addr, strict=False)
944 946 except ValueError:
945 947 raise formencode.Invalid(self.message('badFormat', state),
946 948 value, state)
947 949
948 950 return _validator
949 951
950 952
951 953 def FieldKey():
952 954 class _validator(formencode.validators.FancyValidator):
953 955 messages = {
954 956 'badFormat': _(
955 957 u'Key name can only consist of letters, '
956 958 u'underscore, dash or numbers'),
957 959 }
958 960
959 961 def validate_python(self, value, state):
960 962 if not re.match('[a-zA-Z0-9_-]+$', value):
961 963 raise formencode.Invalid(self.message('badFormat', state),
962 964 value, state)
963 965 return _validator
964 966
965 967
966 968 def BasePath():
967 969 class _validator(formencode.validators.FancyValidator):
968 970 messages = {
969 971 'badPath': _(u'Filename cannot be inside a directory'),
970 972 }
971 973
972 974 def _to_python(self, value, state):
973 975 return value
974 976
975 977 def validate_python(self, value, state):
976 978 if value != os.path.basename(value):
977 979 raise formencode.Invalid(self.message('badPath', state),
978 980 value, state)
979 981 return _validator
980 982
981 983
982 984 def ValidAuthPlugins():
983 985 class _validator(formencode.validators.FancyValidator):
984 986 messages = {
985 987 'import_duplicate': _(
986 988 u'Plugins %(loaded)s and %(next_to_load)s '
987 989 u'both export the same name'),
990 'missing_includeme': _(
991 u'The plugin "%(plugin_id)s" is missing an includeme '
992 u'function.'),
993 'import_error': _(
994 u'Can not load plugin "%(plugin_id)s"'),
995 'no_plugin': _(
996 u'No plugin available with ID "%(plugin_id)s"'),
988 997 }
989 998
990 999 def _to_python(self, value, state):
991 1000 # filter empty values
992 1001 return filter(lambda s: s not in [None, ''], value)
993 1002
994 def validate_python(self, value, state):
995 from rhodecode.authentication.base import loadplugin
996 module_list = value
997 unique_names = {}
1003 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1004 """
1005 Validates that the plugin import works. It also checks that the
1006 plugin has an includeme attribute.
1007 """
998 1008 try:
999 for module in module_list:
1000 if module.startswith(legacy_plugin_prefix):
1001 continue
1002 plugin = loadplugin(module)
1003 plugin_name = plugin.name
1004 if plugin_name in unique_names:
1005 msg = M(self, 'import_duplicate', state,
1006 loaded=unique_names[plugin_name],
1007 next_to_load=plugin_name)
1008 raise formencode.Invalid(msg, value, state)
1009 unique_names[plugin_name] = plugin
1010 except (KeyError, AttributeError, TypeError) as e:
1011 raise formencode.Invalid(str(e), value, state)
1009 plugin = _import_legacy_plugin(plugin_id)
1010 except Exception as e:
1011 log.exception(
1012 'Exception during import of auth legacy plugin "{}"'
1013 .format(plugin_id))
1014 msg = M(self, 'import_error', plugin_id=plugin_id)
1015 raise formencode.Invalid(msg, value, state)
1016
1017 if not hasattr(plugin, 'includeme'):
1018 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1019 raise formencode.Invalid(msg, value, state)
1020
1021 return plugin
1022
1023 def _validate_plugin_id(self, plugin_id, value, state):
1024 """
1025 Plugins are already imported during app start up. Therefore this
1026 validation only retrieves the plugin from the plugin registry and
1027 if it returns something not None everything is OK.
1028 """
1029 plugin = loadplugin(plugin_id)
1030
1031 if plugin is None:
1032 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1033 raise formencode.Invalid(msg, value, state)
1034
1035 return plugin
1036
1037 def validate_python(self, value, state):
1038 unique_names = {}
1039 for plugin_id in value:
1040
1041 # Validate legacy or normal plugin.
1042 if plugin_id.startswith(legacy_plugin_prefix):
1043 plugin = self._validate_legacy_plugin_id(
1044 plugin_id, value, state)
1045 else:
1046 plugin = self._validate_plugin_id(plugin_id, value, state)
1047
1048 # Only allow unique plugin names.
1049 if plugin.name in unique_names:
1050 msg = M(self, 'import_duplicate', state,
1051 loaded=unique_names[plugin.name],
1052 next_to_load=plugin)
1053 raise formencode.Invalid(msg, value, state)
1054 unique_names[plugin.name] = plugin
1012 1055
1013 1056 return _validator
1014 1057
1015 1058
1016 1059 def UniqGistId():
1017 1060 class _validator(formencode.validators.FancyValidator):
1018 1061 messages = {
1019 1062 'gistid_taken': _(u'This gistid is already in use')
1020 1063 }
1021 1064
1022 1065 def _to_python(self, value, state):
1023 1066 return repo_name_slug(value.lower())
1024 1067
1025 1068 def validate_python(self, value, state):
1026 1069 existing = Gist.get_by_access_id(value)
1027 1070 if existing:
1028 1071 msg = M(self, 'gistid_taken', state)
1029 1072 raise formencode.Invalid(
1030 1073 msg, value, state, error_dict={'gistid': msg}
1031 1074 )
1032 1075
1033 1076 return _validator
1034 1077
1035 1078
1036 1079 def ValidPattern():
1037 1080
1038 1081 class _Validator(formencode.validators.FancyValidator):
1039 1082
1040 1083 def _to_python(self, value, state):
1041 1084 patterns = []
1042 1085
1043 1086 prefix = 'new_pattern'
1044 1087 for name, v in value.iteritems():
1045 1088 pattern_name = '_'.join((prefix, 'pattern'))
1046 1089 if name.startswith(pattern_name):
1047 1090 new_item_id = name[len(pattern_name)+1:]
1048 1091
1049 1092 def _field(name):
1050 1093 return '%s_%s_%s' % (prefix, name, new_item_id)
1051 1094
1052 1095 values = {
1053 1096 'issuetracker_pat': value.get(_field('pattern')),
1054 1097 'issuetracker_pat': value.get(_field('pattern')),
1055 1098 'issuetracker_url': value.get(_field('url')),
1056 1099 'issuetracker_pref': value.get(_field('prefix')),
1057 1100 'issuetracker_desc': value.get(_field('description'))
1058 1101 }
1059 1102 new_uid = md5(values['issuetracker_pat'])
1060 1103
1061 1104 has_required_fields = (
1062 1105 values['issuetracker_pat']
1063 1106 and values['issuetracker_url'])
1064 1107
1065 1108 if has_required_fields:
1066 1109 settings = [
1067 1110 ('_'.join((key, new_uid)), values[key], 'unicode')
1068 1111 for key in values]
1069 1112 patterns.append(settings)
1070 1113
1071 1114 value['patterns'] = patterns
1072 1115 delete_patterns = value.get('uid') or []
1073 1116 if not isinstance(delete_patterns, (list, tuple)):
1074 1117 delete_patterns = [delete_patterns]
1075 1118 value['delete_patterns'] = delete_patterns
1076 1119 return value
1077 1120 return _Validator
General Comments 0
You need to be logged in to leave comments. Login now