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