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