##// END OF EJS Templates
repo-schema: added a custom schema for repo settings....
marcink -
r1719:1b64c19a default
parent child Browse files
Show More
@@ -1,178 +1,179 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import deform
24 24 from pyramid.httpexceptions import HTTPFound
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import RepoAppView
28 28 from rhodecode.forms import RcForm
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator,
33 33 HasRepoPermissionAllDecorator, CSRFRequired)
34 34 from rhodecode.model.db import RepositoryField, RepoGroup
35 35 from rhodecode.model.meta import Session
36 36 from rhodecode.model.repo import RepoModel
37 37 from rhodecode.model.scm import RepoGroupList, ScmModel
38 38 from rhodecode.model.validation_schema.schemas import repo_schema
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 class RepoSettingsView(RepoAppView):
44 44
45 45 def load_default_context(self):
46 46 c = self._get_local_tmpl_context()
47 47
48 48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 49 c.repo_info = self.db_repo
50 50
51 51 acl_groups = RepoGroupList(
52 52 RepoGroup.query().all(),
53 53 perm_set=['group.write', 'group.admin'])
54 54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
56 56
57 57 # in case someone no longer have a group.write access to a repository
58 58 # pre fill the list with this entry, we don't care if this is the same
59 59 # but it will allow saving repo data properly.
60 60 repo_group = self.db_repo.group
61 61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
62 62 c.repo_groups_choices.append(repo_group.group_id)
63 63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
64 64
65 65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
66 66 # we might be in missing requirement state, so we load things
67 67 # without touching scm_instance()
68 68 c.landing_revs_choices, c.landing_revs = \
69 69 ScmModel().get_repo_landing_revs()
70 70 else:
71 71 c.landing_revs_choices, c.landing_revs = \
72 72 ScmModel().get_repo_landing_revs(self.db_repo)
73 73
74 74 c.personal_repo_group = c.auth_user.personal_repo_group
75 75 c.repo_fields = RepositoryField.query()\
76 76 .filter(RepositoryField.repository == self.db_repo).all()
77 77
78 78 self._register_global_c(c)
79 79 return c
80 80
81 81 def _get_schema(self, c, old_values=None):
82 82 return repo_schema.RepoSettingsSchema().bind(
83 repo_type=self.db_repo.repo_type,
83 84 repo_type_options=[self.db_repo.repo_type],
84 85 repo_ref_options=c.landing_revs_choices,
85 86 repo_ref_items=c.landing_revs,
86 87 repo_repo_group_options=c.repo_groups_choices,
87 88 repo_repo_group_items=c.repo_groups,
88 89 # user caller
89 90 user=self._rhodecode_user,
90 91 old_values=old_values
91 92 )
92 93
93 94 @LoginRequired()
94 95 @HasRepoPermissionAnyDecorator('repository.admin')
95 96 @view_config(
96 97 route_name='edit_repo', request_method='GET',
97 98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 99 def edit_settings(self):
99 100 c = self.load_default_context()
100 101 c.active = 'settings'
101 102
102 103 defaults = RepoModel()._get_defaults(self.db_repo_name)
103 104 defaults['repo_owner'] = defaults['user']
104 105 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
105 106
106 107 schema = self._get_schema(c)
107 108 c.form = RcForm(schema, appstruct=defaults)
108 109 return self._get_template_context(c)
109 110
110 111 @LoginRequired()
111 112 @HasRepoPermissionAllDecorator('repository.admin')
112 113 @CSRFRequired()
113 114 @view_config(
114 115 route_name='edit_repo', request_method='POST',
115 116 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
116 117 def edit_settings_update(self):
117 118 _ = self.request.translate
118 119 c = self.load_default_context()
119 120 c.active = 'settings'
120 121 old_repo_name = self.db_repo_name
121 122
122 123 old_values = self.db_repo.get_api_data()
123 124 schema = self._get_schema(c, old_values=old_values)
124 125
125 126 c.form = RcForm(schema)
126 127 pstruct = self.request.POST.items()
127 128 pstruct.append(('repo_type', self.db_repo.repo_type))
128 129 try:
129 130 schema_data = c.form.validate(pstruct)
130 131 except deform.ValidationFailure as err_form:
131 132 return self._get_template_context(c)
132 133
133 134 # data is now VALID, proceed with updates
134 135 # save validated data back into the updates dict
135 136 validated_updates = dict(
136 137 repo_name=schema_data['repo_group']['repo_name_without_group'],
137 138 repo_group=schema_data['repo_group']['repo_group_id'],
138 139
139 140 user=schema_data['repo_owner'],
140 141 repo_description=schema_data['repo_description'],
141 142 repo_private=schema_data['repo_private'],
142 143 clone_uri=schema_data['repo_clone_uri'],
143 144 repo_landing_rev=schema_data['repo_landing_commit_ref'],
144 145 repo_enable_statistics=schema_data['repo_enable_statistics'],
145 146 repo_enable_locking=schema_data['repo_enable_locking'],
146 147 repo_enable_downloads=schema_data['repo_enable_downloads'],
147 148 )
148 149 # detect if CLONE URI changed, if we get OLD means we keep old values
149 150 if schema_data['repo_clone_uri_change'] == 'OLD':
150 151 validated_updates['clone_uri'] = self.db_repo.clone_uri
151 152
152 153 # use the new full name for redirect
153 154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
154 155
155 156 # save extra fields into our validated data
156 157 for key, value in pstruct:
157 158 if key.startswith(RepositoryField.PREFIX):
158 159 validated_updates[key] = value
159 160
160 161 try:
161 162 RepoModel().update(self.db_repo, **validated_updates)
162 163 ScmModel().mark_for_invalidation(new_repo_name)
163 164
164 165 audit_logger.store(
165 166 'repo.edit', action_data={'old_data': old_values},
166 167 user=self._rhodecode_user, repo=self.db_repo)
167 168
168 169 Session().commit()
169 170
170 171 h.flash(_('Repository {} updated successfully').format(
171 172 old_repo_name), category='success')
172 173 except Exception:
173 174 log.exception("Exception during update of repository")
174 175 h.flash(_('Error occurred during update of repository {}').format(
175 176 old_repo_name), category='error')
176 177
177 178 raise HTTPFound(
178 179 self.request.route_path('edit_repo', repo_name=new_repo_name))
@@ -1,360 +1,414 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 import deform.widget
23 23
24 24 from rhodecode.translation import _
25 25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
26 26 from rhodecode.model.validation_schema import validators, preparers, types
27 27
28 28 DEFAULT_LANDING_REF = 'rev:tip'
29 29
30 30
31 31 def get_group_and_repo(repo_name):
32 32 from rhodecode.model.repo_group import RepoGroupModel
33 33 return RepoGroupModel()._get_group_name_and_parent(
34 34 repo_name, get_object=True)
35 35
36 36
37 37 def get_repo_group(repo_group_id):
38 38 from rhodecode.model.repo_group import RepoGroup
39 39 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
40 40
41 41
42 42 @colander.deferred
43 43 def deferred_repo_type_validator(node, kw):
44 44 options = kw.get('repo_type_options', [])
45 45 return colander.OneOf([x for x in options])
46 46
47 47
48 48 @colander.deferred
49 49 def deferred_repo_owner_validator(node, kw):
50 50
51 51 def repo_owner_validator(node, value):
52 52 from rhodecode.model.db import User
53 53 existing = User.get_by_username(value)
54 54 if not existing:
55 55 msg = _(u'Repo owner with id `{}` does not exists').format(value)
56 56 raise colander.Invalid(node, msg)
57 57
58 58 return repo_owner_validator
59 59
60 60
61 61 @colander.deferred
62 62 def deferred_landing_ref_validator(node, kw):
63 63 options = kw.get(
64 64 'repo_ref_options', [DEFAULT_LANDING_REF])
65 65 return colander.OneOf([x for x in options])
66 66
67 67
68 68 @colander.deferred
69 def deferred_clone_uri_validator(node, kw):
70 repo_type = kw.get('repo_type')
71 validator = validators.CloneUriValidator(repo_type)
72 return validator
73
74
75 @colander.deferred
69 76 def deferred_landing_ref_widget(node, kw):
70 77 items = kw.get(
71 78 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)])
72 79 items = convert_to_optgroup(items)
73 80 return deform.widget.Select2Widget(values=items)
74 81
75 82
76 83 @colander.deferred
77 84 def deferred_fork_of_validator(node, kw):
78 85 old_values = kw.get('old_values') or {}
79 86
80 87 def fork_of_validator(node, value):
81 88 from rhodecode.model.db import Repository, RepoGroup
82 89 existing = Repository.get_by_repo_name(value)
83 90 if not existing:
84 91 msg = _(u'Fork with id `{}` does not exists').format(value)
85 92 raise colander.Invalid(node, msg)
86 93 elif old_values['repo_name'] == existing.repo_name:
87 94 msg = _(u'Cannot set fork of '
88 95 u'parameter of this repository to itself').format(value)
89 96 raise colander.Invalid(node, msg)
90 97
91 98 return fork_of_validator
92 99
93 100
94 101 @colander.deferred
95 102 def deferred_can_write_to_group_validator(node, kw):
96 103 request_user = kw.get('user')
97 104 old_values = kw.get('old_values') or {}
98 105
99 106 def can_write_to_group_validator(node, value):
100 107 """
101 108 Checks if given repo path is writable by user. This includes checks if
102 109 user is allowed to create repositories under root path or under
103 110 repo group paths
104 111 """
105 112
106 113 from rhodecode.lib.auth import (
107 114 HasPermissionAny, HasRepoGroupPermissionAny)
108 115 from rhodecode.model.repo_group import RepoGroupModel
109 116
110 117 messages = {
111 118 'invalid_repo_group':
112 119 _(u"Repository group `{}` does not exist"),
113 120 # permissions denied we expose as not existing, to prevent
114 121 # resource discovery
115 122 'permission_denied':
116 123 _(u"Repository group `{}` does not exist"),
117 124 'permission_denied_root':
118 125 _(u"You do not have the permission to store "
119 126 u"repositories in the root location.")
120 127 }
121 128
122 129 value = value['repo_group_name']
123 130
124 131 is_root_location = value is types.RootLocation
125 132 # NOT initialized validators, we must call them
126 133 can_create_repos_at_root = HasPermissionAny(
127 134 'hg.admin', 'hg.create.repository')
128 135
129 136 # if values is root location, we simply need to check if we can write
130 137 # to root location !
131 138 if is_root_location:
132 139 if can_create_repos_at_root(user=request_user):
133 140 # we can create repo group inside tool-level. No more checks
134 141 # are required
135 142 return
136 143 else:
137 144 # "fake" node name as repo_name, otherwise we oddly report
138 145 # the error as if it was coming form repo_group
139 146 # however repo_group is empty when using root location.
140 147 node.name = 'repo_name'
141 148 raise colander.Invalid(node, messages['permission_denied_root'])
142 149
143 150 # parent group not exists ? throw an error
144 151 repo_group = RepoGroupModel().get_by_group_name(value)
145 152 if value and not repo_group:
146 153 raise colander.Invalid(
147 154 node, messages['invalid_repo_group'].format(value))
148 155
149 156 gr_name = repo_group.group_name
150 157
151 158 # create repositories with write permission on group is set to true
152 159 create_on_write = HasPermissionAny(
153 160 'hg.create.write_on_repogroup.true')(user=request_user)
154 161
155 162 group_admin = HasRepoGroupPermissionAny('group.admin')(
156 163 gr_name, 'can write into group validator', user=request_user)
157 164 group_write = HasRepoGroupPermissionAny('group.write')(
158 165 gr_name, 'can write into group validator', user=request_user)
159 166
160 167 forbidden = not (group_admin or (group_write and create_on_write))
161 168
162 169 # TODO: handling of old values, and detecting no-change in path
163 170 # to skip permission checks in such cases. This only needs to be
164 171 # implemented if we use this schema in forms as well
165 172
166 173 # gid = (old_data['repo_group'].get('group_id')
167 174 # if (old_data and 'repo_group' in old_data) else None)
168 175 # value_changed = gid != safe_int(value)
169 176 # new = not old_data
170 177
171 178 # do check if we changed the value, there's a case that someone got
172 179 # revoked write permissions to a repository, he still created, we
173 180 # don't need to check permission if he didn't change the value of
174 181 # groups in form box
175 182 # if value_changed or new:
176 183 # # parent group need to be existing
177 184 # TODO: ENDS HERE
178 185
179 186 if repo_group and forbidden:
180 187 msg = messages['permission_denied'].format(value)
181 188 raise colander.Invalid(node, msg)
182 189
183 190 return can_write_to_group_validator
184 191
185 192
186 193 @colander.deferred
187 194 def deferred_unique_name_validator(node, kw):
188 195 request_user = kw.get('user')
189 196 old_values = kw.get('old_values') or {}
190 197
191 198 def unique_name_validator(node, value):
192 199 from rhodecode.model.db import Repository, RepoGroup
193 200 name_changed = value != old_values.get('repo_name')
194 201
195 202 existing = Repository.get_by_repo_name(value)
196 203 if name_changed and existing:
197 204 msg = _(u'Repository with name `{}` already exists').format(value)
198 205 raise colander.Invalid(node, msg)
199 206
200 207 existing_group = RepoGroup.get_by_group_name(value)
201 208 if name_changed and existing_group:
202 209 msg = _(u'Repository group with name `{}` already exists').format(
203 210 value)
204 211 raise colander.Invalid(node, msg)
205 212 return unique_name_validator
206 213
207 214
208 215 @colander.deferred
209 216 def deferred_repo_name_validator(node, kw):
210 217 def no_git_suffix_validator(node, value):
211 218 if value.endswith('.git'):
212 219 msg = _('Repository name cannot end with .git')
213 220 raise colander.Invalid(node, msg)
214 221 return colander.All(
215 222 no_git_suffix_validator, validators.valid_name_validator)
216 223
217 224
218 225 @colander.deferred
219 226 def deferred_repo_group_validator(node, kw):
220 227 options = kw.get(
221 228 'repo_repo_group_options')
222 229 return colander.OneOf([x for x in options])
223 230
224 231
225 232 @colander.deferred
226 233 def deferred_repo_group_widget(node, kw):
227 234 items = kw.get('repo_repo_group_items')
228 235 return deform.widget.Select2Widget(values=items)
229 236
230 237
231 238 class GroupType(colander.Mapping):
232 239 def _validate(self, node, value):
233 240 try:
234 241 return dict(repo_group_name=value)
235 242 except Exception as e:
236 243 raise colander.Invalid(
237 244 node, '"${val}" is not a mapping type: ${err}'.format(
238 245 val=value, err=e))
239 246
240 247 def deserialize(self, node, cstruct):
241 248 if cstruct is colander.null:
242 249 return cstruct
243 250
244 251 appstruct = super(GroupType, self).deserialize(node, cstruct)
245 252 validated_name = appstruct['repo_group_name']
246 253
247 254 # inject group based on once deserialized data
248 255 (repo_name_without_group,
249 256 parent_group_name,
250 257 parent_group) = get_group_and_repo(validated_name)
251 258
252 259 appstruct['repo_name_with_group'] = validated_name
253 260 appstruct['repo_name_without_group'] = repo_name_without_group
254 261 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
255 262
256 263 if parent_group:
257 264 appstruct['repo_group_id'] = parent_group.group_id
258 265
259 266 return appstruct
260 267
261 268
262 269 class GroupSchema(colander.SchemaNode):
263 270 schema_type = GroupType
264 271 validator = deferred_can_write_to_group_validator
265 272 missing = colander.null
266 273
267 274
268 275 class RepoGroup(GroupSchema):
269 276 repo_group_name = colander.SchemaNode(
270 277 types.GroupNameType())
271 278 repo_group_id = colander.SchemaNode(
272 279 colander.String(), missing=None)
273 280 repo_name_without_group = colander.SchemaNode(
274 281 colander.String(), missing=None)
275 282
276 283
277 284 class RepoGroupAccessSchema(colander.MappingSchema):
278 285 repo_group = RepoGroup()
279 286
280 287
281 288 class RepoNameUniqueSchema(colander.MappingSchema):
282 289 unique_repo_name = colander.SchemaNode(
283 290 colander.String(),
284 291 validator=deferred_unique_name_validator)
285 292
286 293
287 294 class RepoSchema(colander.MappingSchema):
288 295
289 296 repo_name = colander.SchemaNode(
290 297 types.RepoNameType(),
291 298 validator=deferred_repo_name_validator)
292 299
293 300 repo_type = colander.SchemaNode(
294 301 colander.String(),
295 302 validator=deferred_repo_type_validator)
296 303
297 304 repo_owner = colander.SchemaNode(
298 305 colander.String(),
299 306 validator=deferred_repo_owner_validator,
300 307 widget=deform.widget.TextInputWidget())
301 308
302 309 repo_description = colander.SchemaNode(
303 310 colander.String(), missing='',
304 311 widget=deform.widget.TextAreaWidget())
305 312
306 313 repo_landing_commit_ref = colander.SchemaNode(
307 314 colander.String(),
308 315 validator=deferred_landing_ref_validator,
309 316 preparers=[preparers.strip_preparer],
310 317 missing=DEFAULT_LANDING_REF,
311 318 widget=deferred_landing_ref_widget)
312 319
313 320 repo_clone_uri = colander.SchemaNode(
314 321 colander.String(),
315 322 validator=colander.All(colander.Length(min=1)),
316 323 preparers=[preparers.strip_preparer],
317 324 missing='')
318 325
319 326 repo_fork_of = colander.SchemaNode(
320 327 colander.String(),
321 328 validator=deferred_fork_of_validator,
322 329 missing=None)
323 330
324 331 repo_private = colander.SchemaNode(
325 332 types.StringBooleanType(),
326 333 missing=False, widget=deform.widget.CheckboxWidget())
327 334 repo_copy_permissions = colander.SchemaNode(
328 335 types.StringBooleanType(),
329 336 missing=False, widget=deform.widget.CheckboxWidget())
330 337 repo_enable_statistics = colander.SchemaNode(
331 338 types.StringBooleanType(),
332 339 missing=False, widget=deform.widget.CheckboxWidget())
333 340 repo_enable_downloads = colander.SchemaNode(
334 341 types.StringBooleanType(),
335 342 missing=False, widget=deform.widget.CheckboxWidget())
336 343 repo_enable_locking = colander.SchemaNode(
337 344 types.StringBooleanType(),
338 345 missing=False, widget=deform.widget.CheckboxWidget())
339 346
340 347 def deserialize(self, cstruct):
341 348 """
342 349 Custom deserialize that allows to chain validation, and verify
343 350 permissions, and as last step uniqueness
344 351 """
345 352
346 353 # first pass, to validate given data
347 354 appstruct = super(RepoSchema, self).deserialize(cstruct)
348 355 validated_name = appstruct['repo_name']
349 356
350 357 # second pass to validate permissions to repo_group
351 358 second = RepoGroupAccessSchema().bind(**self.bindings)
352 359 appstruct_second = second.deserialize({'repo_group': validated_name})
353 360 # save result
354 361 appstruct['repo_group'] = appstruct_second['repo_group']
355 362
356 363 # thirds to validate uniqueness
357 364 third = RepoNameUniqueSchema().bind(**self.bindings)
358 365 third.deserialize({'unique_repo_name': validated_name})
359 366
360 367 return appstruct
368
369
370 class RepoSettingsSchema(RepoSchema):
371 repo_group = colander.SchemaNode(
372 colander.Integer(),
373 validator=deferred_repo_group_validator,
374 widget=deferred_repo_group_widget,
375 missing='')
376
377 repo_clone_uri_change = colander.SchemaNode(
378 colander.String(),
379 missing='NEW')
380
381 repo_clone_uri = colander.SchemaNode(
382 colander.String(),
383 preparers=[preparers.strip_preparer],
384 validator=deferred_clone_uri_validator,
385 missing='')
386
387 def deserialize(self, cstruct):
388 """
389 Custom deserialize that allows to chain validation, and verify
390 permissions, and as last step uniqueness
391 """
392
393 # first pass, to validate given data
394 appstruct = super(RepoSchema, self).deserialize(cstruct)
395 validated_name = appstruct['repo_name']
396 # because of repoSchema adds repo-group as an ID, we inject it as
397 # full name here because validators require it, it's unwrapped later
398 # so it's safe to use and final name is going to be without group anyway
399
400 group, separator = get_repo_group(appstruct['repo_group'])
401 if group:
402 validated_name = separator.join([group.group_name, validated_name])
403
404 # second pass to validate permissions to repo_group
405 second = RepoGroupAccessSchema().bind(**self.bindings)
406 appstruct_second = second.deserialize({'repo_group': validated_name})
407 # save result
408 appstruct['repo_group'] = appstruct_second['repo_group']
409
410 # thirds to validate uniqueness
411 third = RepoNameUniqueSchema().bind(**self.bindings)
412 third.deserialize({'unique_repo_name': validated_name})
413
414 return appstruct
@@ -1,48 +1,140 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
1 21 import os
2 22 import re
23 import logging
24
3 25
4 26 import ipaddress
5 27 import colander
6 28
7 29 from rhodecode.translation import _
8 30 from rhodecode.lib.utils2 import glob2re
9 31
32 log = logging.getLogger(__name__)
33
10 34
11 35 def ip_addr_validator(node, value):
12 36 try:
13 37 # this raises an ValueError if address is not IpV4 or IpV6
14 38 ipaddress.ip_network(value, strict=False)
15 39 except ValueError:
16 40 msg = _(u'Please enter a valid IPv4 or IpV6 address')
17 41 raise colander.Invalid(node, msg)
18 42
19 43
20 44 class IpAddrValidator(object):
21 45 def __init__(self, strict=True):
22 46 self.strict = strict
23 47
24 48 def __call__(self, node, value):
25 49 try:
26 50 # this raises an ValueError if address is not IpV4 or IpV6
27 51 ipaddress.ip_network(value, strict=self.strict)
28 52 except ValueError:
29 53 msg = _(u'Please enter a valid IPv4 or IpV6 address')
30 54 raise colander.Invalid(node, msg)
31 55
32 56
33 57 def glob_validator(node, value):
34 58 try:
35 59 re.compile('^' + glob2re(value) + '$')
36 60 except Exception:
37 61 msg = _(u'Invalid glob pattern')
38 62 raise colander.Invalid(node, msg)
39 63
40 64
41 65 def valid_name_validator(node, value):
42 66 from rhodecode.model.validation_schema import types
43 67 if value is types.RootLocation:
44 68 return
45 69
46 70 msg = _('Name must start with a letter or number. Got `{}`').format(value)
47 71 if not re.match(r'^[a-zA-z0-9]{1,}', value):
48 72 raise colander.Invalid(node, msg)
73
74
75 class InvalidCloneUrl(Exception):
76 allowed_prefixes = ()
77
78
79 def url_validator(url, repo_type, config):
80 from rhodecode.lib.vcs.backends.hg import MercurialRepository
81 from rhodecode.lib.vcs.backends.git import GitRepository
82 from rhodecode.lib.vcs.backends.svn import SubversionRepository
83
84 if repo_type == 'hg':
85 allowed_prefixes = ('http', 'svn+http', 'git+http')
86
87 if 'http' in url[:4]:
88 # initially check if it's at least the proper URL
89 # or does it pass basic auth
90
91 MercurialRepository.check_url(url, config)
92 elif 'svn+http' in url[:8]: # svn->hg import
93 SubversionRepository.check_url(url, config)
94 elif 'git+http' in url[:8]: # git->hg import
95 raise NotImplementedError()
96 else:
97 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
98 'Allowed url must start with one of %s'
99 % (url, ','.join(allowed_prefixes)))
100 exc.allowed_prefixes = allowed_prefixes
101 raise exc
102
103 elif repo_type == 'git':
104 allowed_prefixes = ('http', 'svn+http', 'hg+http')
105 if 'http' in url[:4]:
106 # initially check if it's at least the proper URL
107 # or does it pass basic auth
108 GitRepository.check_url(url, config)
109 elif 'svn+http' in url[:8]: # svn->git import
110 raise NotImplementedError()
111 elif 'hg+http' in url[:8]: # hg->git import
112 raise NotImplementedError()
113 else:
114 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
115 'Allowed url must start with one of %s'
116 % (url, ','.join(allowed_prefixes)))
117 exc.allowed_prefixes = allowed_prefixes
118 raise exc
119
120
121 class CloneUriValidator(object):
122 def __init__(self, repo_type):
123 self.repo_type = repo_type
124
125 def __call__(self, node, value):
126 from rhodecode.lib.utils import make_db_config
127 try:
128 config = make_db_config(clear_session=False)
129 url_validator(value, self.repo_type, config)
130 except InvalidCloneUrl as e:
131 log.warning(e)
132 msg = _(u'Invalid clone url, provide a valid clone '
133 u'url starting with one of {allowed_prefixes}').format(
134 allowed_prefixes=e.allowed_prefixes)
135 raise colander.Invalid(node, msg)
136 except Exception:
137 log.exception('Url validation failed')
138 msg = _(u'invalid clone url for {repo_type} repository').format(
139 repo_type=self.repo_type)
140 raise colander.Invalid(node, msg)
@@ -1,249 +1,260 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 <div class="panel panel-default">
5 5 <div class="panel-heading">
6 6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 7 </div>
8 8 <div class="panel-body">
9 9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')}
10 10 <div class="form">
11 11 <!-- fields -->
12 12 <div class="fields">
13 13
14 14 <div class="field">
15 15 <div class="label">
16 16 <label for="repo_name">${_('Name')}:</label>
17 17 </div>
18 18 <div class="input">
19 19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
20 20 ${c.form.render_error(request, c.form['repo_name'])|n}
21 21
22 22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
23 23 <p id="clone_id" style="display:none;">
24 24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
25 25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
26 26 Using above url guarantees that this repository will always be accessible under such url.
27 27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="repo_group">${_('Repository group')}:</label>
34 34 </div>
35 35 <div class="select">
36 36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 37 ${c.form.render_error(request, c.form['repo_group'])|n}
38 38
39 39 % if c.personal_repo_group:
40 40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 42 </a>
43 43 % endif
44 44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 45 </div>
46 46 </div>
47 47
48 48 % if c.rhodecode_db_repo.repo_type != 'svn':
49 49 <div class="field">
50 50 <div class="label">
51 51 <label for="clone_uri">${_('Remote uri')}:</label>
52 52 </div>
53 53 <div class="input">
54 54 %if c.rhodecode_db_repo.clone_uri:
55 ## display
55 ## display, if we don't have any errors
56 % if not c.form['repo_clone_uri'].error:
56 57 <div id="clone_uri_hidden" class='text-as-placeholder'>
57 58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
58 59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
59 60 </div>
61 % endif
62
60 63 ## alter field
61 <div id="alter_clone_uri" style="display: none">
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
62 65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
63 66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
64 ${h.hidden('repo_clone_uri_change', 'OLD')}
67 % if c.form['repo_clone_uri'].error:
68 ## we got error from form subit, means we modify the url
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
70 % else:
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
72 % endif
65 73
74 % if not c.form['repo_clone_uri'].error:
66 75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 % endif
77
67 78 </div>
68 79 %else:
69 80 ## not set yet, display form to set it
70 81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
71 82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
72 83 ${h.hidden('repo_clone_uri_change', 'NEW')}
73 84 %endif
74 85 <p id="alter_clone_uri_help_block" class="help-block">
75 86 <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %>
76 87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
77 88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
78 89 </p>
79 90 </div>
80 91 </div>
81 92 % else:
82 93 ${h.hidden('repo_clone_uri', '')}
83 94 % endif
84 95
85 96 <div class="field">
86 97 <div class="label">
87 98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
88 99 </div>
89 100 <div class="select">
90 101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
91 102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
92 103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
93 104 </div>
94 105 </div>
95 106
96 107 <div class="field badged-field">
97 108 <div class="label">
98 109 <label for="repo_owner">${_('Owner')}:</label>
99 110 </div>
100 111 <div class="input">
101 112 <div class="badge-input-container">
102 113 <div class="user-badge">
103 114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, show_disabled=not c.rhodecode_db_repo.user.active)}
104 115 </div>
105 116 <div class="badge-input-wrap">
106 117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
107 118 </div>
108 119 </div>
109 120 ${c.form.render_error(request, c.form['repo_owner'])|n}
110 121 <p class="help-block">${_('Change owner of this repository.')}</p>
111 122 </div>
112 123 </div>
113 124
114 125 <div class="field">
115 126 <div class="label label-textarea">
116 127 <label for="repo_description">${_('Description')}:</label>
117 128 </div>
118 129 <div class="textarea text-area editor">
119 130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
120 131 ${c.form.render_error(request, c.form['repo_description'])|n}
121 132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
122 133 </div>
123 134 </div>
124 135
125 136 <div class="field">
126 137 <div class="label label-checkbox">
127 138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
128 139 </div>
129 140 <div class="checkboxes">
130 141 ${c.form['repo_private'].render(css_class='medium')|n}
131 142 ${c.form.render_error(request, c.form['repo_private'])|n}
132 143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
133 144 </div>
134 145 </div>
135 146 <div class="field">
136 147 <div class="label label-checkbox">
137 148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
138 149 </div>
139 150 <div class="checkboxes">
140 151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
141 152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
142 153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
143 154 </div>
144 155 </div>
145 156 <div class="field">
146 157 <div class="label label-checkbox">
147 158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
148 159 </div>
149 160 <div class="checkboxes">
150 161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
151 162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
152 163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
153 164 </div>
154 165 </div>
155 166 <div class="field">
156 167 <div class="label label-checkbox">
157 168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
158 169 </div>
159 170 <div class="checkboxes">
160 171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
161 172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
162 173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
163 174 </div>
164 175 </div>
165 176
166 177 %if c.visual.repository_fields:
167 178 ## EXTRA FIELDS
168 179 %for field in c.repo_fields:
169 180 <div class="field">
170 181 <div class="label">
171 182 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
172 183 </div>
173 184 <div class="input input-medium">
174 185 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
175 186 %if field.field_desc:
176 187 <span class="help-block">${field.field_desc}</span>
177 188 %endif
178 189 </div>
179 190 </div>
180 191 %endfor
181 192 %endif
182 193 <div class="buttons">
183 194 ${h.submit('save',_('Save'),class_="btn")}
184 195 ${h.reset('reset',_('Reset'),class_="btn")}
185 196 </div>
186 197 </div>
187 198 </div>
188 199 ${h.end_form()}
189 200 </div>
190 201 </div>
191 202
192 203 <script>
193 204 $(document).ready(function(){
194 205 var cloneUrl = function() {
195 206 var alterButton = $('#alter_clone_uri');
196 207 var editButton = $('#edit_clone_uri');
197 208 var cancelEditButton = $('#cancel_edit_clone_uri');
198 209 var hiddenUrl = $('#clone_uri_hidden');
199 210 var hiddenUrlValue = $('#clone_uri_hidden_value');
200 211 var input = $('#clone_uri');
201 212 var helpBlock = $('#alter_clone_uri_help_block');
202 213 var changedFlag = $('#repo_clone_uri_change');
203 214 var originalText = helpBlock.html();
204 215 var obfuscatedUrl = hiddenUrlValue.html();
205 216
206 217 var edit = function(e) {
207 218 alterButton.show();
208 219 editButton.hide();
209 220 hiddenUrl.hide();
210 221
211 222 //add the old value next to input for verification
212 223 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
213 224 changedFlag.val('MOD');
214 225 };
215 226
216 227 var cancelEdit = function(e) {
217 228 alterButton.hide();
218 229 editButton.show();
219 230 hiddenUrl.show();
220 231
221 232 helpBlock.html(originalText);
222 233 changedFlag.val('OLD');
223 234 input.val('');
224 235 };
225 236
226 237 var initEvents = function() {
227 238 editButton.on('click', edit);
228 239 cancelEditButton.on('click', cancelEdit);
229 240 };
230 241
231 242 var setInitialState = function() {
232 243 if (input.hasClass('error')) {
233 244 alterButton.show();
234 245 editButton.hide();
235 246 hiddenUrl.hide();
236 247 }
237 248 };
238 249
239 250 setInitialState();
240 251 initEvents();
241 252 }();
242 253
243 254 selectMyGroup = function(element) {
244 255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
245 256 };
246 257
247 258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
248 259 });
249 260 </script>
General Comments 0
You need to be logged in to leave comments. Login now