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