##// END OF EJS Templates
validators: apply username validator to prevent bad values beeing searched in DB, and potential XSS payload sent via validators.
super-admin -
r4706:732ede7c stable
parent child Browse files
Show More
@@ -1,311 +1,313 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 import colander
22 import colander
23 import deform.widget
23 import deform.widget
24
24
25 from rhodecode.model.validation_schema.utils import username_converter
25 from rhodecode.translation import _
26 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import validators, preparers, types
27 from rhodecode.model.validation_schema import validators, preparers, types
27
28
28
29
29 def get_group_and_repo(repo_name):
30 def get_group_and_repo(repo_name):
30 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
31 return RepoGroupModel()._get_group_name_and_parent(
32 return RepoGroupModel()._get_group_name_and_parent(
32 repo_name, get_object=True)
33 repo_name, get_object=True)
33
34
34
35
35 def get_repo_group(repo_group_id):
36 def get_repo_group(repo_group_id):
36 from rhodecode.model.repo_group import RepoGroup
37 from rhodecode.model.repo_group import RepoGroup
37 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
38 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
38
39
39
40
40 @colander.deferred
41 @colander.deferred
41 def deferred_can_write_to_group_validator(node, kw):
42 def deferred_can_write_to_group_validator(node, kw):
42 old_values = kw.get('old_values') or {}
43 old_values = kw.get('old_values') or {}
43 request_user = kw.get('user')
44 request_user = kw.get('user')
44
45
45 def can_write_group_validator(node, value):
46 def can_write_group_validator(node, value):
46 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
47 HasPermissionAny, HasRepoGroupPermissionAny)
48 HasPermissionAny, HasRepoGroupPermissionAny)
48 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
49
50
50 messages = {
51 messages = {
51 'invalid_parent_repo_group':
52 'invalid_parent_repo_group':
52 _(u"Parent repository group `{}` does not exist"),
53 _(u"Parent repository group `{}` does not exist"),
53 # permissions denied we expose as not existing, to prevent
54 # permissions denied we expose as not existing, to prevent
54 # resource discovery
55 # resource discovery
55 'permission_denied_parent_group':
56 'permission_denied_parent_group':
56 _(u"You do not have the permissions to store "
57 _(u"You do not have the permissions to store "
57 u"repository groups inside repository group `{}`"),
58 u"repository groups inside repository group `{}`"),
58 'permission_denied_root':
59 'permission_denied_root':
59 _(u"You do not have the permission to store "
60 _(u"You do not have the permission to store "
60 u"repository groups in the root location.")
61 u"repository groups in the root location.")
61 }
62 }
62
63
63 value = value['repo_group_name']
64 value = value['repo_group_name']
64 parent_group_name = value
65 parent_group_name = value
65
66
66 is_root_location = value is types.RootLocation
67 is_root_location = value is types.RootLocation
67
68
68 # NOT initialized validators, we must call them
69 # NOT initialized validators, we must call them
69 can_create_repo_groups_at_root = HasPermissionAny(
70 can_create_repo_groups_at_root = HasPermissionAny(
70 'hg.admin', 'hg.repogroup.create.true')
71 'hg.admin', 'hg.repogroup.create.true')
71
72
72 if is_root_location:
73 if is_root_location:
73 if can_create_repo_groups_at_root(user=request_user):
74 if can_create_repo_groups_at_root(user=request_user):
74 # we can create repo group inside tool-level. No more checks
75 # we can create repo group inside tool-level. No more checks
75 # are required
76 # are required
76 return
77 return
77 else:
78 else:
78 raise colander.Invalid(node, messages['permission_denied_root'])
79 raise colander.Invalid(node, messages['permission_denied_root'])
79
80
80 # check if the parent repo group actually exists
81 # check if the parent repo group actually exists
81 parent_group = None
82 parent_group = None
82 if parent_group_name:
83 if parent_group_name:
83 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
84 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
84 if value and not parent_group:
85 if value and not parent_group:
85 raise colander.Invalid(
86 raise colander.Invalid(
86 node, messages['invalid_parent_repo_group'].format(
87 node, messages['invalid_parent_repo_group'].format(
87 parent_group_name))
88 parent_group_name))
88
89
89 # check if we have permissions to create new groups under
90 # check if we have permissions to create new groups under
90 # parent repo group
91 # parent repo group
91 # create repositories with write permission on group is set to true
92 # create repositories with write permission on group is set to true
92 create_on_write = HasPermissionAny(
93 create_on_write = HasPermissionAny(
93 'hg.create.write_on_repogroup.true')(user=request_user)
94 'hg.create.write_on_repogroup.true')(user=request_user)
94
95
95 group_admin = HasRepoGroupPermissionAny('group.admin')(
96 group_admin = HasRepoGroupPermissionAny('group.admin')(
96 parent_group_name, 'can write into group validator', user=request_user)
97 parent_group_name, 'can write into group validator', user=request_user)
97 group_write = HasRepoGroupPermissionAny('group.write')(
98 group_write = HasRepoGroupPermissionAny('group.write')(
98 parent_group_name, 'can write into group validator', user=request_user)
99 parent_group_name, 'can write into group validator', user=request_user)
99
100
100 # creation by write access is currently disabled. Needs thinking if
101 # creation by write access is currently disabled. Needs thinking if
101 # we want to allow this...
102 # we want to allow this...
102 forbidden = not (group_admin or (group_write and create_on_write and 0))
103 forbidden = not (group_admin or (group_write and create_on_write and 0))
103
104
104 old_name = old_values.get('group_name')
105 old_name = old_values.get('group_name')
105 if old_name and old_name == old_values.get('submitted_repo_group_name'):
106 if old_name and old_name == old_values.get('submitted_repo_group_name'):
106 # we're editing a repository group, we didn't change the name
107 # we're editing a repository group, we didn't change the name
107 # we skip the check for write into parent group now
108 # we skip the check for write into parent group now
108 # this allows changing settings for this repo group
109 # this allows changing settings for this repo group
109 return
110 return
110
111
111 if parent_group and forbidden:
112 if parent_group and forbidden:
112 msg = messages['permission_denied_parent_group'].format(parent_group_name)
113 msg = messages['permission_denied_parent_group'].format(parent_group_name)
113 raise colander.Invalid(node, msg)
114 raise colander.Invalid(node, msg)
114
115
115 return can_write_group_validator
116 return can_write_group_validator
116
117
117
118
118 @colander.deferred
119 @colander.deferred
119 def deferred_repo_group_owner_validator(node, kw):
120 def deferred_repo_group_owner_validator(node, kw):
120
121
121 def repo_owner_validator(node, value):
122 def repo_owner_validator(node, value):
122 from rhodecode.model.db import User
123 from rhodecode.model.db import User
124 value = username_converter(value)
123 existing = User.get_by_username(value)
125 existing = User.get_by_username(value)
124 if not existing:
126 if not existing:
125 msg = _(u'Repo group owner with id `{}` does not exists').format(
127 msg = _(u'Repo group owner with id `{}` does not exists').format(
126 value)
128 value)
127 raise colander.Invalid(node, msg)
129 raise colander.Invalid(node, msg)
128
130
129 return repo_owner_validator
131 return repo_owner_validator
130
132
131
133
132 @colander.deferred
134 @colander.deferred
133 def deferred_unique_name_validator(node, kw):
135 def deferred_unique_name_validator(node, kw):
134 request_user = kw.get('user')
136 request_user = kw.get('user')
135 old_values = kw.get('old_values') or {}
137 old_values = kw.get('old_values') or {}
136
138
137 def unique_name_validator(node, value):
139 def unique_name_validator(node, value):
138 from rhodecode.model.db import Repository, RepoGroup
140 from rhodecode.model.db import Repository, RepoGroup
139 name_changed = value != old_values.get('group_name')
141 name_changed = value != old_values.get('group_name')
140
142
141 existing = Repository.get_by_repo_name(value)
143 existing = Repository.get_by_repo_name(value)
142 if name_changed and existing:
144 if name_changed and existing:
143 msg = _(u'Repository with name `{}` already exists').format(value)
145 msg = _(u'Repository with name `{}` already exists').format(value)
144 raise colander.Invalid(node, msg)
146 raise colander.Invalid(node, msg)
145
147
146 existing_group = RepoGroup.get_by_group_name(value)
148 existing_group = RepoGroup.get_by_group_name(value)
147 if name_changed and existing_group:
149 if name_changed and existing_group:
148 msg = _(u'Repository group with name `{}` already exists').format(
150 msg = _(u'Repository group with name `{}` already exists').format(
149 value)
151 value)
150 raise colander.Invalid(node, msg)
152 raise colander.Invalid(node, msg)
151 return unique_name_validator
153 return unique_name_validator
152
154
153
155
154 @colander.deferred
156 @colander.deferred
155 def deferred_repo_group_name_validator(node, kw):
157 def deferred_repo_group_name_validator(node, kw):
156 return validators.valid_name_validator
158 return validators.valid_name_validator
157
159
158
160
159 @colander.deferred
161 @colander.deferred
160 def deferred_repo_group_validator(node, kw):
162 def deferred_repo_group_validator(node, kw):
161 options = kw.get(
163 options = kw.get(
162 'repo_group_repo_group_options')
164 'repo_group_repo_group_options')
163 return colander.OneOf([x for x in options])
165 return colander.OneOf([x for x in options])
164
166
165
167
166 @colander.deferred
168 @colander.deferred
167 def deferred_repo_group_widget(node, kw):
169 def deferred_repo_group_widget(node, kw):
168 items = kw.get('repo_group_repo_group_items')
170 items = kw.get('repo_group_repo_group_items')
169 return deform.widget.Select2Widget(values=items)
171 return deform.widget.Select2Widget(values=items)
170
172
171
173
172 class GroupType(colander.Mapping):
174 class GroupType(colander.Mapping):
173 def _validate(self, node, value):
175 def _validate(self, node, value):
174 try:
176 try:
175 return dict(repo_group_name=value)
177 return dict(repo_group_name=value)
176 except Exception as e:
178 except Exception as e:
177 raise colander.Invalid(
179 raise colander.Invalid(
178 node, '"${val}" is not a mapping type: ${err}'.format(
180 node, '"${val}" is not a mapping type: ${err}'.format(
179 val=value, err=e))
181 val=value, err=e))
180
182
181 def deserialize(self, node, cstruct):
183 def deserialize(self, node, cstruct):
182 if cstruct is colander.null:
184 if cstruct is colander.null:
183 return cstruct
185 return cstruct
184
186
185 appstruct = super(GroupType, self).deserialize(node, cstruct)
187 appstruct = super(GroupType, self).deserialize(node, cstruct)
186 validated_name = appstruct['repo_group_name']
188 validated_name = appstruct['repo_group_name']
187
189
188 # inject group based on once deserialized data
190 # inject group based on once deserialized data
189 (repo_group_name_without_group,
191 (repo_group_name_without_group,
190 parent_group_name,
192 parent_group_name,
191 parent_group) = get_group_and_repo(validated_name)
193 parent_group) = get_group_and_repo(validated_name)
192
194
193 appstruct['repo_group_name_with_group'] = validated_name
195 appstruct['repo_group_name_with_group'] = validated_name
194 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
196 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
195 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
197 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
196 if parent_group:
198 if parent_group:
197 appstruct['repo_group_id'] = parent_group.group_id
199 appstruct['repo_group_id'] = parent_group.group_id
198
200
199 return appstruct
201 return appstruct
200
202
201
203
202 class GroupSchema(colander.SchemaNode):
204 class GroupSchema(colander.SchemaNode):
203 schema_type = GroupType
205 schema_type = GroupType
204 validator = deferred_can_write_to_group_validator
206 validator = deferred_can_write_to_group_validator
205 missing = colander.null
207 missing = colander.null
206
208
207
209
208 class RepoGroup(GroupSchema):
210 class RepoGroup(GroupSchema):
209 repo_group_name = colander.SchemaNode(
211 repo_group_name = colander.SchemaNode(
210 types.GroupNameType())
212 types.GroupNameType())
211 repo_group_id = colander.SchemaNode(
213 repo_group_id = colander.SchemaNode(
212 colander.String(), missing=None)
214 colander.String(), missing=None)
213 repo_group_name_without_group = colander.SchemaNode(
215 repo_group_name_without_group = colander.SchemaNode(
214 colander.String(), missing=None)
216 colander.String(), missing=None)
215
217
216
218
217 class RepoGroupAccessSchema(colander.MappingSchema):
219 class RepoGroupAccessSchema(colander.MappingSchema):
218 repo_group = RepoGroup()
220 repo_group = RepoGroup()
219
221
220
222
221 class RepoGroupNameUniqueSchema(colander.MappingSchema):
223 class RepoGroupNameUniqueSchema(colander.MappingSchema):
222 unique_repo_group_name = colander.SchemaNode(
224 unique_repo_group_name = colander.SchemaNode(
223 colander.String(),
225 colander.String(),
224 validator=deferred_unique_name_validator)
226 validator=deferred_unique_name_validator)
225
227
226
228
227 class RepoGroupSchema(colander.Schema):
229 class RepoGroupSchema(colander.Schema):
228
230
229 repo_group_name = colander.SchemaNode(
231 repo_group_name = colander.SchemaNode(
230 types.GroupNameType(),
232 types.GroupNameType(),
231 validator=deferred_repo_group_name_validator)
233 validator=deferred_repo_group_name_validator)
232
234
233 repo_group_owner = colander.SchemaNode(
235 repo_group_owner = colander.SchemaNode(
234 colander.String(),
236 colander.String(),
235 validator=deferred_repo_group_owner_validator)
237 validator=deferred_repo_group_owner_validator)
236
238
237 repo_group_description = colander.SchemaNode(
239 repo_group_description = colander.SchemaNode(
238 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
240 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
239
241
240 repo_group_copy_permissions = colander.SchemaNode(
242 repo_group_copy_permissions = colander.SchemaNode(
241 types.StringBooleanType(),
243 types.StringBooleanType(),
242 missing=False, widget=deform.widget.CheckboxWidget())
244 missing=False, widget=deform.widget.CheckboxWidget())
243
245
244 repo_group_enable_locking = colander.SchemaNode(
246 repo_group_enable_locking = colander.SchemaNode(
245 types.StringBooleanType(),
247 types.StringBooleanType(),
246 missing=False, widget=deform.widget.CheckboxWidget())
248 missing=False, widget=deform.widget.CheckboxWidget())
247
249
248 def deserialize(self, cstruct):
250 def deserialize(self, cstruct):
249 """
251 """
250 Custom deserialize that allows to chain validation, and verify
252 Custom deserialize that allows to chain validation, and verify
251 permissions, and as last step uniqueness
253 permissions, and as last step uniqueness
252 """
254 """
253
255
254 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
256 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
255 validated_name = appstruct['repo_group_name']
257 validated_name = appstruct['repo_group_name']
256
258
257 # second pass to validate permissions to repo_group
259 # second pass to validate permissions to repo_group
258 if 'old_values' in self.bindings:
260 if 'old_values' in self.bindings:
259 # save current repo name for name change checks
261 # save current repo name for name change checks
260 self.bindings['old_values']['submitted_repo_group_name'] = validated_name
262 self.bindings['old_values']['submitted_repo_group_name'] = validated_name
261 second = RepoGroupAccessSchema().bind(**self.bindings)
263 second = RepoGroupAccessSchema().bind(**self.bindings)
262 appstruct_second = second.deserialize({'repo_group': validated_name})
264 appstruct_second = second.deserialize({'repo_group': validated_name})
263 # save result
265 # save result
264 appstruct['repo_group'] = appstruct_second['repo_group']
266 appstruct['repo_group'] = appstruct_second['repo_group']
265
267
266 # thirds to validate uniqueness
268 # thirds to validate uniqueness
267 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
269 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
268 third.deserialize({'unique_repo_group_name': validated_name})
270 third.deserialize({'unique_repo_group_name': validated_name})
269
271
270 return appstruct
272 return appstruct
271
273
272
274
273 class RepoGroupSettingsSchema(RepoGroupSchema):
275 class RepoGroupSettingsSchema(RepoGroupSchema):
274 repo_group = colander.SchemaNode(
276 repo_group = colander.SchemaNode(
275 colander.Integer(),
277 colander.Integer(),
276 validator=deferred_repo_group_validator,
278 validator=deferred_repo_group_validator,
277 widget=deferred_repo_group_widget,
279 widget=deferred_repo_group_widget,
278 missing='')
280 missing='')
279
281
280 def deserialize(self, cstruct):
282 def deserialize(self, cstruct):
281 """
283 """
282 Custom deserialize that allows to chain validation, and verify
284 Custom deserialize that allows to chain validation, and verify
283 permissions, and as last step uniqueness
285 permissions, and as last step uniqueness
284 """
286 """
285
287
286 # first pass, to validate given data
288 # first pass, to validate given data
287 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
289 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
288 validated_name = appstruct['repo_group_name']
290 validated_name = appstruct['repo_group_name']
289
291
290 # because of repoSchema adds repo-group as an ID, we inject it as
292 # because of repoSchema adds repo-group as an ID, we inject it as
291 # full name here because validators require it, it's unwrapped later
293 # full name here because validators require it, it's unwrapped later
292 # so it's safe to use and final name is going to be without group anyway
294 # so it's safe to use and final name is going to be without group anyway
293
295
294 group, separator = get_repo_group(appstruct['repo_group'])
296 group, separator = get_repo_group(appstruct['repo_group'])
295 if group:
297 if group:
296 validated_name = separator.join([group.group_name, validated_name])
298 validated_name = separator.join([group.group_name, validated_name])
297
299
298 # second pass to validate permissions to repo_group
300 # second pass to validate permissions to repo_group
299 if 'old_values' in self.bindings:
301 if 'old_values' in self.bindings:
300 # save current repo name for name change checks
302 # save current repo name for name change checks
301 self.bindings['old_values']['submitted_repo_group_name'] = validated_name
303 self.bindings['old_values']['submitted_repo_group_name'] = validated_name
302 second = RepoGroupAccessSchema().bind(**self.bindings)
304 second = RepoGroupAccessSchema().bind(**self.bindings)
303 appstruct_second = second.deserialize({'repo_group': validated_name})
305 appstruct_second = second.deserialize({'repo_group': validated_name})
304 # save result
306 # save result
305 appstruct['repo_group'] = appstruct_second['repo_group']
307 appstruct['repo_group'] = appstruct_second['repo_group']
306
308
307 # thirds to validate uniqueness
309 # thirds to validate uniqueness
308 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
310 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
309 third.deserialize({'unique_repo_group_name': validated_name})
311 third.deserialize({'unique_repo_group_name': validated_name})
310
312
311 return appstruct
313 return appstruct
@@ -1,453 +1,454 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 import colander
21 import colander
22 import deform.widget
22 import deform.widget
23
23
24 from rhodecode.translation import _
24 from rhodecode.translation import _
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup, username_converter
26 from rhodecode.model.validation_schema import validators, preparers, types
26 from rhodecode.model.validation_schema import validators, preparers, types
27
27
28 DEFAULT_LANDING_REF = 'rev:tip'
28 DEFAULT_LANDING_REF = 'rev:tip'
29 DEFAULT_BACKEND_LANDING_REF = {
29 DEFAULT_BACKEND_LANDING_REF = {
30 'hg': 'branch:default',
30 'hg': 'branch:default',
31 'git': 'branch:master',
31 'git': 'branch:master',
32 'svn': 'rev:tip',
32 'svn': 'rev:tip',
33 }
33 }
34
34
35
35
36 def get_group_and_repo(repo_name):
36 def get_group_and_repo(repo_name):
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
38 return RepoGroupModel()._get_group_name_and_parent(
38 return RepoGroupModel()._get_group_name_and_parent(
39 repo_name, get_object=True)
39 repo_name, get_object=True)
40
40
41
41
42 def get_repo_group(repo_group_id):
42 def get_repo_group(repo_group_id):
43 from rhodecode.model.repo_group import RepoGroup
43 from rhodecode.model.repo_group import RepoGroup
44 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
44 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
45
45
46
46
47 @colander.deferred
47 @colander.deferred
48 def deferred_repo_type_validator(node, kw):
48 def deferred_repo_type_validator(node, kw):
49 options = kw.get('repo_type_options', [])
49 options = kw.get('repo_type_options', [])
50 return colander.OneOf([x for x in options])
50 return colander.OneOf([x for x in options])
51
51
52
52
53 @colander.deferred
53 @colander.deferred
54 def deferred_repo_owner_validator(node, kw):
54 def deferred_repo_owner_validator(node, kw):
55
55
56 def repo_owner_validator(node, value):
56 def repo_owner_validator(node, value):
57 from rhodecode.model.db import User
57 from rhodecode.model.db import User
58 value = username_converter(value)
58 existing = User.get_by_username(value)
59 existing = User.get_by_username(value)
59 if not existing:
60 if not existing:
60 msg = _(u'Repo owner with id `{}` does not exists').format(value)
61 msg = _(u'Repo owner with id `{}` does not exists').format(value)
61 raise colander.Invalid(node, msg)
62 raise colander.Invalid(node, msg)
62
63
63 return repo_owner_validator
64 return repo_owner_validator
64
65
65
66
66 @colander.deferred
67 @colander.deferred
67 def deferred_landing_ref_validator(node, kw):
68 def deferred_landing_ref_validator(node, kw):
68 options = kw.get(
69 options = kw.get(
69 'repo_ref_options', [DEFAULT_LANDING_REF])
70 'repo_ref_options', [DEFAULT_LANDING_REF])
70 return colander.OneOf([x for x in options])
71 return colander.OneOf([x for x in options])
71
72
72
73
73 @colander.deferred
74 @colander.deferred
74 def deferred_sync_uri_validator(node, kw):
75 def deferred_sync_uri_validator(node, kw):
75 repo_type = kw.get('repo_type')
76 repo_type = kw.get('repo_type')
76 validator = validators.CloneUriValidator(repo_type)
77 validator = validators.CloneUriValidator(repo_type)
77 return validator
78 return validator
78
79
79
80
80 @colander.deferred
81 @colander.deferred
81 def deferred_landing_ref_widget(node, kw):
82 def deferred_landing_ref_widget(node, kw):
82 repo_type = kw.get('repo_type')
83 repo_type = kw.get('repo_type')
83 default_opts = []
84 default_opts = []
84 if repo_type:
85 if repo_type:
85 default_opts.append(
86 default_opts.append(
86 (DEFAULT_BACKEND_LANDING_REF[repo_type],
87 (DEFAULT_BACKEND_LANDING_REF[repo_type],
87 DEFAULT_BACKEND_LANDING_REF[repo_type]))
88 DEFAULT_BACKEND_LANDING_REF[repo_type]))
88
89
89 items = kw.get('repo_ref_items', default_opts)
90 items = kw.get('repo_ref_items', default_opts)
90 items = convert_to_optgroup(items)
91 items = convert_to_optgroup(items)
91 return deform.widget.Select2Widget(values=items)
92 return deform.widget.Select2Widget(values=items)
92
93
93
94
94 @colander.deferred
95 @colander.deferred
95 def deferred_fork_of_validator(node, kw):
96 def deferred_fork_of_validator(node, kw):
96 old_values = kw.get('old_values') or {}
97 old_values = kw.get('old_values') or {}
97
98
98 def fork_of_validator(node, value):
99 def fork_of_validator(node, value):
99 from rhodecode.model.db import Repository, RepoGroup
100 from rhodecode.model.db import Repository, RepoGroup
100 existing = Repository.get_by_repo_name(value)
101 existing = Repository.get_by_repo_name(value)
101 if not existing:
102 if not existing:
102 msg = _(u'Fork with id `{}` does not exists').format(value)
103 msg = _(u'Fork with id `{}` does not exists').format(value)
103 raise colander.Invalid(node, msg)
104 raise colander.Invalid(node, msg)
104 elif old_values['repo_name'] == existing.repo_name:
105 elif old_values['repo_name'] == existing.repo_name:
105 msg = _(u'Cannot set fork of '
106 msg = _(u'Cannot set fork of '
106 u'parameter of this repository to itself').format(value)
107 u'parameter of this repository to itself').format(value)
107 raise colander.Invalid(node, msg)
108 raise colander.Invalid(node, msg)
108
109
109 return fork_of_validator
110 return fork_of_validator
110
111
111
112
112 @colander.deferred
113 @colander.deferred
113 def deferred_can_write_to_group_validator(node, kw):
114 def deferred_can_write_to_group_validator(node, kw):
114 request_user = kw.get('user')
115 request_user = kw.get('user')
115 old_values = kw.get('old_values') or {}
116 old_values = kw.get('old_values') or {}
116
117
117 def can_write_to_group_validator(node, value):
118 def can_write_to_group_validator(node, value):
118 """
119 """
119 Checks if given repo path is writable by user. This includes checks if
120 Checks if given repo path is writable by user. This includes checks if
120 user is allowed to create repositories under root path or under
121 user is allowed to create repositories under root path or under
121 repo group paths
122 repo group paths
122 """
123 """
123
124
124 from rhodecode.lib.auth import (
125 from rhodecode.lib.auth import (
125 HasPermissionAny, HasRepoGroupPermissionAny)
126 HasPermissionAny, HasRepoGroupPermissionAny)
126 from rhodecode.model.repo_group import RepoGroupModel
127 from rhodecode.model.repo_group import RepoGroupModel
127
128
128 messages = {
129 messages = {
129 'invalid_repo_group':
130 'invalid_repo_group':
130 _(u"Repository group `{}` does not exist"),
131 _(u"Repository group `{}` does not exist"),
131 # permissions denied we expose as not existing, to prevent
132 # permissions denied we expose as not existing, to prevent
132 # resource discovery
133 # resource discovery
133 'permission_denied':
134 'permission_denied':
134 _(u"Repository group `{}` does not exist"),
135 _(u"Repository group `{}` does not exist"),
135 'permission_denied_root':
136 'permission_denied_root':
136 _(u"You do not have the permission to store "
137 _(u"You do not have the permission to store "
137 u"repositories in the root location.")
138 u"repositories in the root location.")
138 }
139 }
139
140
140 value = value['repo_group_name']
141 value = value['repo_group_name']
141
142
142 is_root_location = value is types.RootLocation
143 is_root_location = value is types.RootLocation
143 # NOT initialized validators, we must call them
144 # NOT initialized validators, we must call them
144 can_create_repos_at_root = HasPermissionAny('hg.admin', 'hg.create.repository')
145 can_create_repos_at_root = HasPermissionAny('hg.admin', 'hg.create.repository')
145
146
146 # if values is root location, we simply need to check if we can write
147 # if values is root location, we simply need to check if we can write
147 # to root location !
148 # to root location !
148 if is_root_location:
149 if is_root_location:
149
150
150 if can_create_repos_at_root(user=request_user):
151 if can_create_repos_at_root(user=request_user):
151 # we can create repo group inside tool-level. No more checks
152 # we can create repo group inside tool-level. No more checks
152 # are required
153 # are required
153 return
154 return
154 else:
155 else:
155 old_name = old_values.get('repo_name')
156 old_name = old_values.get('repo_name')
156 if old_name and old_name == old_values.get('submitted_repo_name'):
157 if old_name and old_name == old_values.get('submitted_repo_name'):
157 # since we didn't change the name, we can skip validation and
158 # since we didn't change the name, we can skip validation and
158 # allow current users without store-in-root permissions to update
159 # allow current users without store-in-root permissions to update
159 return
160 return
160
161
161 # "fake" node name as repo_name, otherwise we oddly report
162 # "fake" node name as repo_name, otherwise we oddly report
162 # the error as if it was coming form repo_group
163 # the error as if it was coming form repo_group
163 # however repo_group is empty when using root location.
164 # however repo_group is empty when using root location.
164 node.name = 'repo_name'
165 node.name = 'repo_name'
165 raise colander.Invalid(node, messages['permission_denied_root'])
166 raise colander.Invalid(node, messages['permission_denied_root'])
166
167
167 # parent group not exists ? throw an error
168 # parent group not exists ? throw an error
168 repo_group = RepoGroupModel().get_by_group_name(value)
169 repo_group = RepoGroupModel().get_by_group_name(value)
169 if value and not repo_group:
170 if value and not repo_group:
170 raise colander.Invalid(
171 raise colander.Invalid(
171 node, messages['invalid_repo_group'].format(value))
172 node, messages['invalid_repo_group'].format(value))
172
173
173 gr_name = repo_group.group_name
174 gr_name = repo_group.group_name
174
175
175 # create repositories with write permission on group is set to true
176 # create repositories with write permission on group is set to true
176 create_on_write = HasPermissionAny(
177 create_on_write = HasPermissionAny(
177 'hg.create.write_on_repogroup.true')(user=request_user)
178 'hg.create.write_on_repogroup.true')(user=request_user)
178
179
179 group_admin = HasRepoGroupPermissionAny('group.admin')(
180 group_admin = HasRepoGroupPermissionAny('group.admin')(
180 gr_name, 'can write into group validator', user=request_user)
181 gr_name, 'can write into group validator', user=request_user)
181 group_write = HasRepoGroupPermissionAny('group.write')(
182 group_write = HasRepoGroupPermissionAny('group.write')(
182 gr_name, 'can write into group validator', user=request_user)
183 gr_name, 'can write into group validator', user=request_user)
183
184
184 forbidden = not (group_admin or (group_write and create_on_write))
185 forbidden = not (group_admin or (group_write and create_on_write))
185
186
186 # TODO: handling of old values, and detecting no-change in path
187 # TODO: handling of old values, and detecting no-change in path
187 # to skip permission checks in such cases. This only needs to be
188 # to skip permission checks in such cases. This only needs to be
188 # implemented if we use this schema in forms as well
189 # implemented if we use this schema in forms as well
189
190
190 # gid = (old_data['repo_group'].get('group_id')
191 # gid = (old_data['repo_group'].get('group_id')
191 # if (old_data and 'repo_group' in old_data) else None)
192 # if (old_data and 'repo_group' in old_data) else None)
192 # value_changed = gid != safe_int(value)
193 # value_changed = gid != safe_int(value)
193 # new = not old_data
194 # new = not old_data
194
195
195 # do check if we changed the value, there's a case that someone got
196 # do check if we changed the value, there's a case that someone got
196 # revoked write permissions to a repository, he still created, we
197 # revoked write permissions to a repository, he still created, we
197 # don't need to check permission if he didn't change the value of
198 # don't need to check permission if he didn't change the value of
198 # groups in form box
199 # groups in form box
199 # if value_changed or new:
200 # if value_changed or new:
200 # # parent group need to be existing
201 # # parent group need to be existing
201 # TODO: ENDS HERE
202 # TODO: ENDS HERE
202
203
203 if repo_group and forbidden:
204 if repo_group and forbidden:
204 msg = messages['permission_denied'].format(value)
205 msg = messages['permission_denied'].format(value)
205 raise colander.Invalid(node, msg)
206 raise colander.Invalid(node, msg)
206
207
207 return can_write_to_group_validator
208 return can_write_to_group_validator
208
209
209
210
210 @colander.deferred
211 @colander.deferred
211 def deferred_unique_name_validator(node, kw):
212 def deferred_unique_name_validator(node, kw):
212 request_user = kw.get('user')
213 request_user = kw.get('user')
213 old_values = kw.get('old_values') or {}
214 old_values = kw.get('old_values') or {}
214
215
215 def unique_name_validator(node, value):
216 def unique_name_validator(node, value):
216 from rhodecode.model.db import Repository, RepoGroup
217 from rhodecode.model.db import Repository, RepoGroup
217 name_changed = value != old_values.get('repo_name')
218 name_changed = value != old_values.get('repo_name')
218
219
219 existing = Repository.get_by_repo_name(value)
220 existing = Repository.get_by_repo_name(value)
220 if name_changed and existing:
221 if name_changed and existing:
221 msg = _(u'Repository with name `{}` already exists').format(value)
222 msg = _(u'Repository with name `{}` already exists').format(value)
222 raise colander.Invalid(node, msg)
223 raise colander.Invalid(node, msg)
223
224
224 existing_group = RepoGroup.get_by_group_name(value)
225 existing_group = RepoGroup.get_by_group_name(value)
225 if name_changed and existing_group:
226 if name_changed and existing_group:
226 msg = _(u'Repository group with name `{}` already exists').format(
227 msg = _(u'Repository group with name `{}` already exists').format(
227 value)
228 value)
228 raise colander.Invalid(node, msg)
229 raise colander.Invalid(node, msg)
229 return unique_name_validator
230 return unique_name_validator
230
231
231
232
232 @colander.deferred
233 @colander.deferred
233 def deferred_repo_name_validator(node, kw):
234 def deferred_repo_name_validator(node, kw):
234 def no_git_suffix_validator(node, value):
235 def no_git_suffix_validator(node, value):
235 if value.endswith('.git'):
236 if value.endswith('.git'):
236 msg = _('Repository name cannot end with .git')
237 msg = _('Repository name cannot end with .git')
237 raise colander.Invalid(node, msg)
238 raise colander.Invalid(node, msg)
238 return colander.All(
239 return colander.All(
239 no_git_suffix_validator, validators.valid_name_validator)
240 no_git_suffix_validator, validators.valid_name_validator)
240
241
241
242
242 @colander.deferred
243 @colander.deferred
243 def deferred_repo_group_validator(node, kw):
244 def deferred_repo_group_validator(node, kw):
244 options = kw.get(
245 options = kw.get(
245 'repo_repo_group_options')
246 'repo_repo_group_options')
246 return colander.OneOf([x for x in options])
247 return colander.OneOf([x for x in options])
247
248
248
249
249 @colander.deferred
250 @colander.deferred
250 def deferred_repo_group_widget(node, kw):
251 def deferred_repo_group_widget(node, kw):
251 items = kw.get('repo_repo_group_items')
252 items = kw.get('repo_repo_group_items')
252 return deform.widget.Select2Widget(values=items)
253 return deform.widget.Select2Widget(values=items)
253
254
254
255
255 class GroupType(colander.Mapping):
256 class GroupType(colander.Mapping):
256 def _validate(self, node, value):
257 def _validate(self, node, value):
257 try:
258 try:
258 return dict(repo_group_name=value)
259 return dict(repo_group_name=value)
259 except Exception as e:
260 except Exception as e:
260 raise colander.Invalid(
261 raise colander.Invalid(
261 node, '"${val}" is not a mapping type: ${err}'.format(
262 node, '"${val}" is not a mapping type: ${err}'.format(
262 val=value, err=e))
263 val=value, err=e))
263
264
264 def deserialize(self, node, cstruct):
265 def deserialize(self, node, cstruct):
265 if cstruct is colander.null:
266 if cstruct is colander.null:
266 return cstruct
267 return cstruct
267
268
268 appstruct = super(GroupType, self).deserialize(node, cstruct)
269 appstruct = super(GroupType, self).deserialize(node, cstruct)
269 validated_name = appstruct['repo_group_name']
270 validated_name = appstruct['repo_group_name']
270
271
271 # inject group based on once deserialized data
272 # inject group based on once deserialized data
272 (repo_name_without_group,
273 (repo_name_without_group,
273 parent_group_name,
274 parent_group_name,
274 parent_group) = get_group_and_repo(validated_name)
275 parent_group) = get_group_and_repo(validated_name)
275
276
276 appstruct['repo_name_with_group'] = validated_name
277 appstruct['repo_name_with_group'] = validated_name
277 appstruct['repo_name_without_group'] = repo_name_without_group
278 appstruct['repo_name_without_group'] = repo_name_without_group
278 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
279 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
279
280
280 if parent_group:
281 if parent_group:
281 appstruct['repo_group_id'] = parent_group.group_id
282 appstruct['repo_group_id'] = parent_group.group_id
282
283
283 return appstruct
284 return appstruct
284
285
285
286
286 class GroupSchema(colander.SchemaNode):
287 class GroupSchema(colander.SchemaNode):
287 schema_type = GroupType
288 schema_type = GroupType
288 validator = deferred_can_write_to_group_validator
289 validator = deferred_can_write_to_group_validator
289 missing = colander.null
290 missing = colander.null
290
291
291
292
292 class RepoGroup(GroupSchema):
293 class RepoGroup(GroupSchema):
293 repo_group_name = colander.SchemaNode(
294 repo_group_name = colander.SchemaNode(
294 types.GroupNameType())
295 types.GroupNameType())
295 repo_group_id = colander.SchemaNode(
296 repo_group_id = colander.SchemaNode(
296 colander.String(), missing=None)
297 colander.String(), missing=None)
297 repo_name_without_group = colander.SchemaNode(
298 repo_name_without_group = colander.SchemaNode(
298 colander.String(), missing=None)
299 colander.String(), missing=None)
299
300
300
301
301 class RepoGroupAccessSchema(colander.MappingSchema):
302 class RepoGroupAccessSchema(colander.MappingSchema):
302 repo_group = RepoGroup()
303 repo_group = RepoGroup()
303
304
304
305
305 class RepoNameUniqueSchema(colander.MappingSchema):
306 class RepoNameUniqueSchema(colander.MappingSchema):
306 unique_repo_name = colander.SchemaNode(
307 unique_repo_name = colander.SchemaNode(
307 colander.String(),
308 colander.String(),
308 validator=deferred_unique_name_validator)
309 validator=deferred_unique_name_validator)
309
310
310
311
311 class RepoSchema(colander.MappingSchema):
312 class RepoSchema(colander.MappingSchema):
312
313
313 repo_name = colander.SchemaNode(
314 repo_name = colander.SchemaNode(
314 types.RepoNameType(),
315 types.RepoNameType(),
315 validator=deferred_repo_name_validator)
316 validator=deferred_repo_name_validator)
316
317
317 repo_type = colander.SchemaNode(
318 repo_type = colander.SchemaNode(
318 colander.String(),
319 colander.String(),
319 validator=deferred_repo_type_validator)
320 validator=deferred_repo_type_validator)
320
321
321 repo_owner = colander.SchemaNode(
322 repo_owner = colander.SchemaNode(
322 colander.String(),
323 colander.String(),
323 validator=deferred_repo_owner_validator,
324 validator=deferred_repo_owner_validator,
324 widget=deform.widget.TextInputWidget())
325 widget=deform.widget.TextInputWidget())
325
326
326 repo_description = colander.SchemaNode(
327 repo_description = colander.SchemaNode(
327 colander.String(), missing='',
328 colander.String(), missing='',
328 widget=deform.widget.TextAreaWidget())
329 widget=deform.widget.TextAreaWidget())
329
330
330 repo_landing_commit_ref = colander.SchemaNode(
331 repo_landing_commit_ref = colander.SchemaNode(
331 colander.String(),
332 colander.String(),
332 validator=deferred_landing_ref_validator,
333 validator=deferred_landing_ref_validator,
333 preparers=[preparers.strip_preparer],
334 preparers=[preparers.strip_preparer],
334 missing=DEFAULT_LANDING_REF,
335 missing=DEFAULT_LANDING_REF,
335 widget=deferred_landing_ref_widget)
336 widget=deferred_landing_ref_widget)
336
337
337 repo_clone_uri = colander.SchemaNode(
338 repo_clone_uri = colander.SchemaNode(
338 colander.String(),
339 colander.String(),
339 validator=deferred_sync_uri_validator,
340 validator=deferred_sync_uri_validator,
340 preparers=[preparers.strip_preparer],
341 preparers=[preparers.strip_preparer],
341 missing='')
342 missing='')
342
343
343 repo_push_uri = colander.SchemaNode(
344 repo_push_uri = colander.SchemaNode(
344 colander.String(),
345 colander.String(),
345 validator=deferred_sync_uri_validator,
346 validator=deferred_sync_uri_validator,
346 preparers=[preparers.strip_preparer],
347 preparers=[preparers.strip_preparer],
347 missing='')
348 missing='')
348
349
349 repo_fork_of = colander.SchemaNode(
350 repo_fork_of = colander.SchemaNode(
350 colander.String(),
351 colander.String(),
351 validator=deferred_fork_of_validator,
352 validator=deferred_fork_of_validator,
352 missing=None)
353 missing=None)
353
354
354 repo_private = colander.SchemaNode(
355 repo_private = colander.SchemaNode(
355 types.StringBooleanType(),
356 types.StringBooleanType(),
356 missing=False, widget=deform.widget.CheckboxWidget())
357 missing=False, widget=deform.widget.CheckboxWidget())
357 repo_copy_permissions = colander.SchemaNode(
358 repo_copy_permissions = colander.SchemaNode(
358 types.StringBooleanType(),
359 types.StringBooleanType(),
359 missing=False, widget=deform.widget.CheckboxWidget())
360 missing=False, widget=deform.widget.CheckboxWidget())
360 repo_enable_statistics = colander.SchemaNode(
361 repo_enable_statistics = colander.SchemaNode(
361 types.StringBooleanType(),
362 types.StringBooleanType(),
362 missing=False, widget=deform.widget.CheckboxWidget())
363 missing=False, widget=deform.widget.CheckboxWidget())
363 repo_enable_downloads = colander.SchemaNode(
364 repo_enable_downloads = colander.SchemaNode(
364 types.StringBooleanType(),
365 types.StringBooleanType(),
365 missing=False, widget=deform.widget.CheckboxWidget())
366 missing=False, widget=deform.widget.CheckboxWidget())
366 repo_enable_locking = colander.SchemaNode(
367 repo_enable_locking = colander.SchemaNode(
367 types.StringBooleanType(),
368 types.StringBooleanType(),
368 missing=False, widget=deform.widget.CheckboxWidget())
369 missing=False, widget=deform.widget.CheckboxWidget())
369
370
370 def deserialize(self, cstruct):
371 def deserialize(self, cstruct):
371 """
372 """
372 Custom deserialize that allows to chain validation, and verify
373 Custom deserialize that allows to chain validation, and verify
373 permissions, and as last step uniqueness
374 permissions, and as last step uniqueness
374 """
375 """
375
376
376 # first pass, to validate given data
377 # first pass, to validate given data
377 appstruct = super(RepoSchema, self).deserialize(cstruct)
378 appstruct = super(RepoSchema, self).deserialize(cstruct)
378 validated_name = appstruct['repo_name']
379 validated_name = appstruct['repo_name']
379
380
380 # second pass to validate permissions to repo_group
381 # second pass to validate permissions to repo_group
381 if 'old_values' in self.bindings:
382 if 'old_values' in self.bindings:
382 # save current repo name for name change checks
383 # save current repo name for name change checks
383 self.bindings['old_values']['submitted_repo_name'] = validated_name
384 self.bindings['old_values']['submitted_repo_name'] = validated_name
384 second = RepoGroupAccessSchema().bind(**self.bindings)
385 second = RepoGroupAccessSchema().bind(**self.bindings)
385 appstruct_second = second.deserialize({'repo_group': validated_name})
386 appstruct_second = second.deserialize({'repo_group': validated_name})
386 # save result
387 # save result
387 appstruct['repo_group'] = appstruct_second['repo_group']
388 appstruct['repo_group'] = appstruct_second['repo_group']
388
389
389 # thirds to validate uniqueness
390 # thirds to validate uniqueness
390 third = RepoNameUniqueSchema().bind(**self.bindings)
391 third = RepoNameUniqueSchema().bind(**self.bindings)
391 third.deserialize({'unique_repo_name': validated_name})
392 third.deserialize({'unique_repo_name': validated_name})
392
393
393 return appstruct
394 return appstruct
394
395
395
396
396 class RepoSettingsSchema(RepoSchema):
397 class RepoSettingsSchema(RepoSchema):
397 repo_group = colander.SchemaNode(
398 repo_group = colander.SchemaNode(
398 colander.Integer(),
399 colander.Integer(),
399 validator=deferred_repo_group_validator,
400 validator=deferred_repo_group_validator,
400 widget=deferred_repo_group_widget,
401 widget=deferred_repo_group_widget,
401 missing='')
402 missing='')
402
403
403 repo_clone_uri_change = colander.SchemaNode(
404 repo_clone_uri_change = colander.SchemaNode(
404 colander.String(),
405 colander.String(),
405 missing='NEW')
406 missing='NEW')
406
407
407 repo_clone_uri = colander.SchemaNode(
408 repo_clone_uri = colander.SchemaNode(
408 colander.String(),
409 colander.String(),
409 preparers=[preparers.strip_preparer],
410 preparers=[preparers.strip_preparer],
410 validator=deferred_sync_uri_validator,
411 validator=deferred_sync_uri_validator,
411 missing='')
412 missing='')
412
413
413 repo_push_uri_change = colander.SchemaNode(
414 repo_push_uri_change = colander.SchemaNode(
414 colander.String(),
415 colander.String(),
415 missing='NEW')
416 missing='NEW')
416
417
417 repo_push_uri = colander.SchemaNode(
418 repo_push_uri = colander.SchemaNode(
418 colander.String(),
419 colander.String(),
419 preparers=[preparers.strip_preparer],
420 preparers=[preparers.strip_preparer],
420 validator=deferred_sync_uri_validator,
421 validator=deferred_sync_uri_validator,
421 missing='')
422 missing='')
422
423
423 def deserialize(self, cstruct):
424 def deserialize(self, cstruct):
424 """
425 """
425 Custom deserialize that allows to chain validation, and verify
426 Custom deserialize that allows to chain validation, and verify
426 permissions, and as last step uniqueness
427 permissions, and as last step uniqueness
427 """
428 """
428
429
429 # first pass, to validate given data
430 # first pass, to validate given data
430 appstruct = super(RepoSchema, self).deserialize(cstruct)
431 appstruct = super(RepoSchema, self).deserialize(cstruct)
431 validated_name = appstruct['repo_name']
432 validated_name = appstruct['repo_name']
432 # because of repoSchema adds repo-group as an ID, we inject it as
433 # because of repoSchema adds repo-group as an ID, we inject it as
433 # full name here because validators require it, it's unwrapped later
434 # full name here because validators require it, it's unwrapped later
434 # so it's safe to use and final name is going to be without group anyway
435 # so it's safe to use and final name is going to be without group anyway
435
436
436 group, separator = get_repo_group(appstruct['repo_group'])
437 group, separator = get_repo_group(appstruct['repo_group'])
437 if group:
438 if group:
438 validated_name = separator.join([group.group_name, validated_name])
439 validated_name = separator.join([group.group_name, validated_name])
439
440
440 # second pass to validate permissions to repo_group
441 # second pass to validate permissions to repo_group
441 if 'old_values' in self.bindings:
442 if 'old_values' in self.bindings:
442 # save current repo name for name change checks
443 # save current repo name for name change checks
443 self.bindings['old_values']['submitted_repo_name'] = validated_name
444 self.bindings['old_values']['submitted_repo_name'] = validated_name
444 second = RepoGroupAccessSchema().bind(**self.bindings)
445 second = RepoGroupAccessSchema().bind(**self.bindings)
445 appstruct_second = second.deserialize({'repo_group': validated_name})
446 appstruct_second = second.deserialize({'repo_group': validated_name})
446 # save result
447 # save result
447 appstruct['repo_group'] = appstruct_second['repo_group']
448 appstruct['repo_group'] = appstruct_second['repo_group']
448
449
449 # thirds to validate uniqueness
450 # thirds to validate uniqueness
450 third = RepoNameUniqueSchema().bind(**self.bindings)
451 third = RepoNameUniqueSchema().bind(**self.bindings)
451 third.deserialize({'unique_repo_name': validated_name})
452 third.deserialize({'unique_repo_name': validated_name})
452
453
453 return appstruct
454 return appstruct
@@ -1,78 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 import re
20 import re
21 import colander
21 import colander
22
22
23 from rhodecode.model.validation_schema import types, validators
23 from rhodecode.model.validation_schema import types, validators
24 from rhodecode.model.validation_schema.utils import username_converter
24 from rhodecode.translation import _
25 from rhodecode.translation import _
25
26
26
27
27 @colander.deferred
28 @colander.deferred
28 def deferred_user_group_name_validator(node, kw):
29 def deferred_user_group_name_validator(node, kw):
29
30
30 def name_validator(node, value):
31 def name_validator(node, value):
31
32
32 msg = _('Allowed in name are letters, numbers, and `-`, `_`, `.` '
33 msg = _('Allowed in name are letters, numbers, and `-`, `_`, `.` '
33 'Name must start with a letter or number. Got `{}`').format(value)
34 'Name must start with a letter or number. Got `{}`').format(value)
34
35
35 if not re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value):
36 if not re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value):
36 raise colander.Invalid(node, msg)
37 raise colander.Invalid(node, msg)
37
38
38 return name_validator
39 return name_validator
39
40
40
41
41 @colander.deferred
42 @colander.deferred
42 def deferred_user_group_owner_validator(node, kw):
43 def deferred_user_group_owner_validator(node, kw):
43
44
44 def owner_validator(node, value):
45 def owner_validator(node, value):
45 from rhodecode.model.db import User
46 from rhodecode.model.db import User
47 value = username_converter(value)
46 existing = User.get_by_username(value)
48 existing = User.get_by_username(value)
47 if not existing:
49 if not existing:
48 msg = _(u'User group owner with id `{}` does not exists').format(value)
50 msg = _(u'User group owner with id `{}` does not exists').format(value)
49 raise colander.Invalid(node, msg)
51 raise colander.Invalid(node, msg)
50
52
51 return owner_validator
53 return owner_validator
52
54
53
55
54 class UserGroupSchema(colander.Schema):
56 class UserGroupSchema(colander.Schema):
55
57
56 user_group_name = colander.SchemaNode(
58 user_group_name = colander.SchemaNode(
57 colander.String(),
59 colander.String(),
58 validator=deferred_user_group_name_validator)
60 validator=deferred_user_group_name_validator)
59
61
60 user_group_description = colander.SchemaNode(
62 user_group_description = colander.SchemaNode(
61 colander.String(), missing='')
63 colander.String(), missing='')
62
64
63 user_group_owner = colander.SchemaNode(
65 user_group_owner = colander.SchemaNode(
64 colander.String(),
66 colander.String(),
65 validator=deferred_user_group_owner_validator)
67 validator=deferred_user_group_owner_validator)
66
68
67 user_group_active = colander.SchemaNode(
69 user_group_active = colander.SchemaNode(
68 types.StringBooleanType(),
70 types.StringBooleanType(),
69 missing=False)
71 missing=False)
70
72
71 def deserialize(self, cstruct):
73 def deserialize(self, cstruct):
72 """
74 """
73 Custom deserialize that allows to chain validation, and verify
75 Custom deserialize that allows to chain validation, and verify
74 permissions, and as last step uniqueness
76 permissions, and as last step uniqueness
75 """
77 """
76
78
77 appstruct = super(UserGroupSchema, self).deserialize(cstruct)
79 appstruct = super(UserGroupSchema, self).deserialize(cstruct)
78 return appstruct
80 return appstruct
@@ -1,49 +1,56 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 import deform.widget
21 import deform.widget
22
22
23
23
24 def convert_to_optgroup(items):
24 def convert_to_optgroup(items):
25 """
25 """
26 Convert such format::
26 Convert such format::
27
27
28 [
28 [
29 ['rev:tip', u'latest tip'],
29 ['rev:tip', u'latest tip'],
30 ([(u'branch:default', u'default')], u'Branches'),
30 ([(u'branch:default', u'default')], u'Branches'),
31 ]
31 ]
32
32
33 into one used by deform Select widget::
33 into one used by deform Select widget::
34
34
35 (
35 (
36 ('rev:tip', 'latest tip'),
36 ('rev:tip', 'latest tip'),
37 OptGroup('Branches',
37 OptGroup('Branches',
38 ('branch:default', 'default'),
38 ('branch:default', 'default'),
39 )
39 )
40 """
40 """
41 result = []
41 result = []
42 for value, label in items:
42 for value, label in items:
43 # option group
43 # option group
44 if isinstance(value, (tuple, list)):
44 if isinstance(value, (tuple, list)):
45 result.append(deform.widget.OptGroup(label, *value))
45 result.append(deform.widget.OptGroup(label, *value))
46 else:
46 else:
47 result.append((value, label))
47 result.append((value, label))
48
48
49 return result
49 return result
50
51
52 def username_converter(value):
53 for noise in ('/', ',', '*', '"', "'", '<', '>', '(', ')', '[', ']', ';'):
54 value = value.replace(noise, '')
55
56 return value
General Comments 0
You need to be logged in to leave comments. Login now