##// END OF EJS Templates
repo-groups: fixed a regression for updating nested repository groups
marcink -
r2241:74a4825b stable
parent child Browse files
Show More
@@ -1,183 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 logging
21 import logging
22 import deform
22 import deform
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.apps._base import RepoGroupAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasPermissionAll,
32 LoginRequired, HasPermissionAll,
33 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
33 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.db import Session, RepoGroup
34 from rhodecode.model.db import Session, RepoGroup
35 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoGroupSettingsView(RepoGroupAppView):
42 class RepoGroupSettingsView(RepoGroupAppView):
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45 c.repo_group = self.db_repo_group
45 c.repo_group = self.db_repo_group
46 no_parrent = not c.repo_group.parent_group
46 no_parrent = not c.repo_group.parent_group
47 can_create_in_root = self._can_create_repo_group()
47 can_create_in_root = self._can_create_repo_group()
48
48
49 show_root_location = False
49 show_root_location = False
50 if no_parrent or can_create_in_root:
50 if no_parrent or can_create_in_root:
51 # we're global admin, we're ok and we can create TOP level groups
51 # we're global admin, we're ok and we can create TOP level groups
52 # or in case this group is already at top-level we also allow
52 # or in case this group is already at top-level we also allow
53 # creation in root
53 # creation in root
54 show_root_location = True
54 show_root_location = True
55
55
56 acl_groups = RepoGroupList(
56 acl_groups = RepoGroupList(
57 RepoGroup.query().all(),
57 RepoGroup.query().all(),
58 perm_set=['group.admin'])
58 perm_set=['group.admin'])
59 c.repo_groups = RepoGroup.groups_choices(
59 c.repo_groups = RepoGroup.groups_choices(
60 groups=acl_groups,
60 groups=acl_groups,
61 show_empty_group=show_root_location)
61 show_empty_group=show_root_location)
62 # filter out current repo group
62 # filter out current repo group
63 exclude_group_ids = [c.repo_group.group_id]
63 exclude_group_ids = [c.repo_group.group_id]
64 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
64 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
65 c.repo_groups)
65 c.repo_groups)
66 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
66 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
67
67
68 parent_group = c.repo_group.parent_group
68 parent_group = c.repo_group.parent_group
69
69
70 add_parent_group = (parent_group and (
70 add_parent_group = (parent_group and (
71 parent_group.group_id not in c.repo_groups_choices))
71 parent_group.group_id not in c.repo_groups_choices))
72 if add_parent_group:
72 if add_parent_group:
73 c.repo_groups_choices.append(parent_group.group_id)
73 c.repo_groups_choices.append(parent_group.group_id)
74 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
74 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
75
75
76 self._register_global_c(c)
76 self._register_global_c(c)
77 return c
77 return c
78
78
79 def _can_create_repo_group(self, parent_group_id=None):
79 def _can_create_repo_group(self, parent_group_id=None):
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
81 create_repo_group = HasPermissionAll(
81 create_repo_group = HasPermissionAll(
82 'hg.repogroup.create.true')('group create controller')
82 'hg.repogroup.create.true')('group create controller')
83 if is_admin or (create_repo_group and not parent_group_id):
83 if is_admin or (create_repo_group and not parent_group_id):
84 # we're global admin, or we have global repo group create
84 # we're global admin, or we have global repo group create
85 # permission
85 # permission
86 # we're ok and we can create TOP level groups
86 # we're ok and we can create TOP level groups
87 return True
87 return True
88 elif parent_group_id:
88 elif parent_group_id:
89 # we check the permission if we can write to parent group
89 # we check the permission if we can write to parent group
90 group = RepoGroup.get(parent_group_id)
90 group = RepoGroup.get(parent_group_id)
91 group_name = group.group_name if group else None
91 group_name = group.group_name if group else None
92 if HasRepoGroupPermissionAny('group.admin')(
92 if HasRepoGroupPermissionAny('group.admin')(
93 group_name, 'check if user is an admin of group'):
93 group_name, 'check if user is an admin of group'):
94 # we're an admin of passed in group, we're ok.
94 # we're an admin of passed in group, we're ok.
95 return True
95 return True
96 else:
96 else:
97 return False
97 return False
98 return False
98 return False
99
99
100 def _get_schema(self, c, old_values=None):
100 def _get_schema(self, c, old_values=None):
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
102 repo_group_repo_group_options=c.repo_groups_choices,
102 repo_group_repo_group_options=c.repo_groups_choices,
103 repo_group_repo_group_items=c.repo_groups,
103 repo_group_repo_group_items=c.repo_groups,
104
104
105 # user caller
105 # user caller
106 user=self._rhodecode_user,
106 user=self._rhodecode_user,
107 old_values=old_values
107 old_values=old_values
108 )
108 )
109
109
110 @LoginRequired()
110 @LoginRequired()
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
112 @view_config(
112 @view_config(
113 route_name='edit_repo_group', request_method='GET',
113 route_name='edit_repo_group', request_method='GET',
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
115 def edit_settings(self):
115 def edit_settings(self):
116 c = self.load_default_context()
116 c = self.load_default_context()
117 c.active = 'settings'
117 c.active = 'settings'
118
118
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
120 defaults['repo_group_owner'] = defaults['user']
120 defaults['repo_group_owner'] = defaults['user']
121
121
122 schema = self._get_schema(c)
122 schema = self._get_schema(c)
123 c.form = RcForm(schema, appstruct=defaults)
123 c.form = RcForm(schema, appstruct=defaults)
124 return self._get_template_context(c)
124 return self._get_template_context(c)
125
125
126 @LoginRequired()
126 @LoginRequired()
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
128 @CSRFRequired()
128 @CSRFRequired()
129 @view_config(
129 @view_config(
130 route_name='edit_repo_group', request_method='POST',
130 route_name='edit_repo_group', request_method='POST',
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
132 def edit_settings_update(self):
132 def edit_settings_update(self):
133 _ = self.request.translate
133 _ = self.request.translate
134 c = self.load_default_context()
134 c = self.load_default_context()
135 c.active = 'settings'
135 c.active = 'settings'
136
136
137 old_repo_group_name = self.db_repo_group_name
137 old_repo_group_name = self.db_repo_group_name
138 new_repo_group_name = old_repo_group_name
138 new_repo_group_name = old_repo_group_name
139
139
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
141 schema = self._get_schema(c, old_values=old_values)
141 schema = self._get_schema(c, old_values=old_values)
142
142
143 c.form = RcForm(schema)
143 c.form = RcForm(schema)
144 pstruct = self.request.POST.items()
144 pstruct = self.request.POST.items()
145
145
146 try:
146 try:
147 schema_data = c.form.validate(pstruct)
147 schema_data = c.form.validate(pstruct)
148 except deform.ValidationFailure as err_form:
148 except deform.ValidationFailure as err_form:
149 return self._get_template_context(c)
149 return self._get_template_context(c)
150
150
151 # data is now VALID, proceed with updates
151 # data is now VALID, proceed with updates
152 # save validated data back into the updates dict
152 # save validated data back into the updates dict
153 validated_updates = dict(
153 validated_updates = dict(
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
156 user=schema_data['repo_group_owner'],
156 user=schema_data['repo_group_owner'],
157 group_description=schema_data['repo_group_description'],
157 group_description=schema_data['repo_group_description'],
158 enable_locking=schema_data['repo_group_enable_locking'],
158 enable_locking=schema_data['repo_group_enable_locking'],
159 )
159 )
160
160
161 try:
161 try:
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
163
163
164 audit_logger.store_web(
164 audit_logger.store_web(
165 'repo_group.edit', action_data={'old_data': old_values},
165 'repo_group.edit', action_data={'old_data': old_values},
166 user=c.rhodecode_user)
166 user=c.rhodecode_user)
167
167
168 Session().commit()
168 Session().commit()
169
169
170 # use the new full name for redirect once we know we updated
170 # use the new full name for redirect once we know we updated
171 # the name on filesystem and in DB
171 # the name on filesystem and in DB
172 new_repo_group_name = schema_data['repo_group_name']
172 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
173
173
174 h.flash(_('Repository Group `{}` updated successfully').format(
174 h.flash(_('Repository Group `{}` updated successfully').format(
175 old_repo_group_name), category='success')
175 old_repo_group_name), category='success')
176
176
177 except Exception:
177 except Exception:
178 log.exception("Exception during update or repository group")
178 log.exception("Exception during update or repository group")
179 h.flash(_('Error occurred during update of repository group %s')
179 h.flash(_('Error occurred during update of repository group %s')
180 % old_repo_group_name, category='error')
180 % old_repo_group_name, category='error')
181
181
182 raise HTTPFound(
182 raise HTTPFound(
183 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
183 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
@@ -1,284 +1,298 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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.translation import _
25 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import validators, preparers, types
26 from rhodecode.model.validation_schema import validators, preparers, types
27
27
28
28
29 def get_group_and_repo(repo_name):
29 def get_group_and_repo(repo_name):
30 from rhodecode.model.repo_group import RepoGroupModel
30 from rhodecode.model.repo_group import RepoGroupModel
31 return RepoGroupModel()._get_group_name_and_parent(
31 return RepoGroupModel()._get_group_name_and_parent(
32 repo_name, get_object=True)
32 repo_name, get_object=True)
33
33
34
34
35 def get_repo_group(repo_group_id):
36 from rhodecode.model.repo_group import RepoGroup
37 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
38
39
35 @colander.deferred
40 @colander.deferred
36 def deferred_can_write_to_group_validator(node, kw):
41 def deferred_can_write_to_group_validator(node, kw):
37 old_values = kw.get('old_values') or {}
42 old_values = kw.get('old_values') or {}
38 request_user = kw.get('user')
43 request_user = kw.get('user')
39
44
40 def can_write_group_validator(node, value):
45 def can_write_group_validator(node, value):
41 from rhodecode.lib.auth import (
46 from rhodecode.lib.auth import (
42 HasPermissionAny, HasRepoGroupPermissionAny)
47 HasPermissionAny, HasRepoGroupPermissionAny)
43 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
44
49
45 messages = {
50 messages = {
46 'invalid_parent_repo_group':
51 'invalid_parent_repo_group':
47 _(u"Parent repository group `{}` does not exist"),
52 _(u"Parent repository group `{}` does not exist"),
48 # permissions denied we expose as not existing, to prevent
53 # permissions denied we expose as not existing, to prevent
49 # resource discovery
54 # resource discovery
50 'permission_denied_parent_group':
55 'permission_denied_parent_group':
51 _(u"Parent repository group `{}` does not exist"),
56 _(u"Parent repository group `{}` does not exist"),
52 'permission_denied_root':
57 'permission_denied_root':
53 _(u"You do not have the permission to store "
58 _(u"You do not have the permission to store "
54 u"repository groups in the root location.")
59 u"repository groups in the root location.")
55 }
60 }
56
61
57 value = value['repo_group_name']
62 value = value['repo_group_name']
58 parent_group_name = value
63 parent_group_name = value
59
64
60 is_root_location = value is types.RootLocation
65 is_root_location = value is types.RootLocation
61
66
62 # NOT initialized validators, we must call them
67 # NOT initialized validators, we must call them
63 can_create_repo_groups_at_root = HasPermissionAny(
68 can_create_repo_groups_at_root = HasPermissionAny(
64 'hg.admin', 'hg.repogroup.create.true')
69 'hg.admin', 'hg.repogroup.create.true')
65
70
66 if is_root_location:
71 if is_root_location:
67 if can_create_repo_groups_at_root(user=request_user):
72 if can_create_repo_groups_at_root(user=request_user):
68 # we can create repo group inside tool-level. No more checks
73 # we can create repo group inside tool-level. No more checks
69 # are required
74 # are required
70 return
75 return
71 else:
76 else:
72 raise colander.Invalid(node, messages['permission_denied_root'])
77 raise colander.Invalid(node, messages['permission_denied_root'])
73
78
74 # check if the parent repo group actually exists
79 # check if the parent repo group actually exists
75 parent_group = None
80 parent_group = None
76 if parent_group_name:
81 if parent_group_name:
77 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
82 parent_group = RepoGroupModel().get_by_group_name(parent_group_name)
78 if value and not parent_group:
83 if value and not parent_group:
79 raise colander.Invalid(
84 raise colander.Invalid(
80 node, messages['invalid_parent_repo_group'].format(
85 node, messages['invalid_parent_repo_group'].format(
81 parent_group_name))
86 parent_group_name))
82
87
83 # check if we have permissions to create new groups under
88 # check if we have permissions to create new groups under
84 # parent repo group
89 # parent repo group
85 # create repositories with write permission on group is set to true
90 # create repositories with write permission on group is set to true
86 create_on_write = HasPermissionAny(
91 create_on_write = HasPermissionAny(
87 'hg.create.write_on_repogroup.true')(user=request_user)
92 'hg.create.write_on_repogroup.true')(user=request_user)
88
93
89 group_admin = HasRepoGroupPermissionAny('group.admin')(
94 group_admin = HasRepoGroupPermissionAny('group.admin')(
90 parent_group_name, 'can write into group validator', user=request_user)
95 parent_group_name, 'can write into group validator', user=request_user)
91 group_write = HasRepoGroupPermissionAny('group.write')(
96 group_write = HasRepoGroupPermissionAny('group.write')(
92 parent_group_name, 'can write into group validator', user=request_user)
97 parent_group_name, 'can write into group validator', user=request_user)
93
98
94 # creation by write access is currently disabled. Needs thinking if
99 # creation by write access is currently disabled. Needs thinking if
95 # we want to allow this...
100 # we want to allow this...
96 forbidden = not (group_admin or (group_write and create_on_write and 0))
101 forbidden = not (group_admin or (group_write and create_on_write and 0))
97
102
98 if parent_group and forbidden:
103 if parent_group and forbidden:
99 msg = messages['permission_denied_parent_group'].format(
104 msg = messages['permission_denied_parent_group'].format(
100 parent_group_name)
105 parent_group_name)
101 raise colander.Invalid(node, msg)
106 raise colander.Invalid(node, msg)
102
107
103 return can_write_group_validator
108 return can_write_group_validator
104
109
105
110
106 @colander.deferred
111 @colander.deferred
107 def deferred_repo_group_owner_validator(node, kw):
112 def deferred_repo_group_owner_validator(node, kw):
108
113
109 def repo_owner_validator(node, value):
114 def repo_owner_validator(node, value):
110 from rhodecode.model.db import User
115 from rhodecode.model.db import User
111 existing = User.get_by_username(value)
116 existing = User.get_by_username(value)
112 if not existing:
117 if not existing:
113 msg = _(u'Repo group owner with id `{}` does not exists').format(
118 msg = _(u'Repo group owner with id `{}` does not exists').format(
114 value)
119 value)
115 raise colander.Invalid(node, msg)
120 raise colander.Invalid(node, msg)
116
121
117 return repo_owner_validator
122 return repo_owner_validator
118
123
119
124
120 @colander.deferred
125 @colander.deferred
121 def deferred_unique_name_validator(node, kw):
126 def deferred_unique_name_validator(node, kw):
122 request_user = kw.get('user')
127 request_user = kw.get('user')
123 old_values = kw.get('old_values') or {}
128 old_values = kw.get('old_values') or {}
124
129
125 def unique_name_validator(node, value):
130 def unique_name_validator(node, value):
126 from rhodecode.model.db import Repository, RepoGroup
131 from rhodecode.model.db import Repository, RepoGroup
127 name_changed = value != old_values.get('group_name')
132 name_changed = value != old_values.get('group_name')
128
133
129 existing = Repository.get_by_repo_name(value)
134 existing = Repository.get_by_repo_name(value)
130 if name_changed and existing:
135 if name_changed and existing:
131 msg = _(u'Repository with name `{}` already exists').format(value)
136 msg = _(u'Repository with name `{}` already exists').format(value)
132 raise colander.Invalid(node, msg)
137 raise colander.Invalid(node, msg)
133
138
134 existing_group = RepoGroup.get_by_group_name(value)
139 existing_group = RepoGroup.get_by_group_name(value)
135 if name_changed and existing_group:
140 if name_changed and existing_group:
136 msg = _(u'Repository group with name `{}` already exists').format(
141 msg = _(u'Repository group with name `{}` already exists').format(
137 value)
142 value)
138 raise colander.Invalid(node, msg)
143 raise colander.Invalid(node, msg)
139 return unique_name_validator
144 return unique_name_validator
140
145
141
146
142 @colander.deferred
147 @colander.deferred
143 def deferred_repo_group_name_validator(node, kw):
148 def deferred_repo_group_name_validator(node, kw):
144 return validators.valid_name_validator
149 return validators.valid_name_validator
145
150
146
151
147 @colander.deferred
152 @colander.deferred
148 def deferred_repo_group_validator(node, kw):
153 def deferred_repo_group_validator(node, kw):
149 options = kw.get(
154 options = kw.get(
150 'repo_group_repo_group_options')
155 'repo_group_repo_group_options')
151 return colander.OneOf([x for x in options])
156 return colander.OneOf([x for x in options])
152
157
153
158
154 @colander.deferred
159 @colander.deferred
155 def deferred_repo_group_widget(node, kw):
160 def deferred_repo_group_widget(node, kw):
156 items = kw.get('repo_group_repo_group_items')
161 items = kw.get('repo_group_repo_group_items')
157 return deform.widget.Select2Widget(values=items)
162 return deform.widget.Select2Widget(values=items)
158
163
159
164
160 class GroupType(colander.Mapping):
165 class GroupType(colander.Mapping):
161 def _validate(self, node, value):
166 def _validate(self, node, value):
162 try:
167 try:
163 return dict(repo_group_name=value)
168 return dict(repo_group_name=value)
164 except Exception as e:
169 except Exception as e:
165 raise colander.Invalid(
170 raise colander.Invalid(
166 node, '"${val}" is not a mapping type: ${err}'.format(
171 node, '"${val}" is not a mapping type: ${err}'.format(
167 val=value, err=e))
172 val=value, err=e))
168
173
169 def deserialize(self, node, cstruct):
174 def deserialize(self, node, cstruct):
170 if cstruct is colander.null:
175 if cstruct is colander.null:
171 return cstruct
176 return cstruct
172
177
173 appstruct = super(GroupType, self).deserialize(node, cstruct)
178 appstruct = super(GroupType, self).deserialize(node, cstruct)
174 validated_name = appstruct['repo_group_name']
179 validated_name = appstruct['repo_group_name']
175
180
176 # inject group based on once deserialized data
181 # inject group based on once deserialized data
177 (repo_group_name_without_group,
182 (repo_group_name_without_group,
178 parent_group_name,
183 parent_group_name,
179 parent_group) = get_group_and_repo(validated_name)
184 parent_group) = get_group_and_repo(validated_name)
180
185
186 appstruct['repo_group_name_with_group'] = validated_name
181 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
187 appstruct['repo_group_name_without_group'] = repo_group_name_without_group
182 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
188 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
183 if parent_group:
189 if parent_group:
184 appstruct['repo_group_id'] = parent_group.group_id
190 appstruct['repo_group_id'] = parent_group.group_id
185
191
186 return appstruct
192 return appstruct
187
193
188
194
189 class GroupSchema(colander.SchemaNode):
195 class GroupSchema(colander.SchemaNode):
190 schema_type = GroupType
196 schema_type = GroupType
191 validator = deferred_can_write_to_group_validator
197 validator = deferred_can_write_to_group_validator
192 missing = colander.null
198 missing = colander.null
193
199
194
200
195 class RepoGroup(GroupSchema):
201 class RepoGroup(GroupSchema):
196 repo_group_name = colander.SchemaNode(
202 repo_group_name = colander.SchemaNode(
197 types.GroupNameType())
203 types.GroupNameType())
198 repo_group_id = colander.SchemaNode(
204 repo_group_id = colander.SchemaNode(
199 colander.String(), missing=None)
205 colander.String(), missing=None)
200 repo_group_name_without_group = colander.SchemaNode(
206 repo_group_name_without_group = colander.SchemaNode(
201 colander.String(), missing=None)
207 colander.String(), missing=None)
202
208
203
209
204 class RepoGroupAccessSchema(colander.MappingSchema):
210 class RepoGroupAccessSchema(colander.MappingSchema):
205 repo_group = RepoGroup()
211 repo_group = RepoGroup()
206
212
207
213
208 class RepoGroupNameUniqueSchema(colander.MappingSchema):
214 class RepoGroupNameUniqueSchema(colander.MappingSchema):
209 unique_repo_group_name = colander.SchemaNode(
215 unique_repo_group_name = colander.SchemaNode(
210 colander.String(),
216 colander.String(),
211 validator=deferred_unique_name_validator)
217 validator=deferred_unique_name_validator)
212
218
213
219
214 class RepoGroupSchema(colander.Schema):
220 class RepoGroupSchema(colander.Schema):
215
221
216 repo_group_name = colander.SchemaNode(
222 repo_group_name = colander.SchemaNode(
217 types.GroupNameType(),
223 types.GroupNameType(),
218 validator=deferred_repo_group_name_validator)
224 validator=deferred_repo_group_name_validator)
219
225
220 repo_group_owner = colander.SchemaNode(
226 repo_group_owner = colander.SchemaNode(
221 colander.String(),
227 colander.String(),
222 validator=deferred_repo_group_owner_validator)
228 validator=deferred_repo_group_owner_validator)
223
229
224 repo_group_description = colander.SchemaNode(
230 repo_group_description = colander.SchemaNode(
225 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
231 colander.String(), missing='', widget=deform.widget.TextAreaWidget())
226
232
227 repo_group_copy_permissions = colander.SchemaNode(
233 repo_group_copy_permissions = colander.SchemaNode(
228 types.StringBooleanType(),
234 types.StringBooleanType(),
229 missing=False, widget=deform.widget.CheckboxWidget())
235 missing=False, widget=deform.widget.CheckboxWidget())
230
236
231 repo_group_enable_locking = colander.SchemaNode(
237 repo_group_enable_locking = colander.SchemaNode(
232 types.StringBooleanType(),
238 types.StringBooleanType(),
233 missing=False, widget=deform.widget.CheckboxWidget())
239 missing=False, widget=deform.widget.CheckboxWidget())
234
240
235 def deserialize(self, cstruct):
241 def deserialize(self, cstruct):
236 """
242 """
237 Custom deserialize that allows to chain validation, and verify
243 Custom deserialize that allows to chain validation, and verify
238 permissions, and as last step uniqueness
244 permissions, and as last step uniqueness
239 """
245 """
240
246
241 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
247 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
242 validated_name = appstruct['repo_group_name']
248 validated_name = appstruct['repo_group_name']
243
249
244 # second pass to validate permissions to repo_group
250 # second pass to validate permissions to repo_group
245 second = RepoGroupAccessSchema().bind(**self.bindings)
251 second = RepoGroupAccessSchema().bind(**self.bindings)
246 appstruct_second = second.deserialize({'repo_group': validated_name})
252 appstruct_second = second.deserialize({'repo_group': validated_name})
247 # save result
253 # save result
248 appstruct['repo_group'] = appstruct_second['repo_group']
254 appstruct['repo_group'] = appstruct_second['repo_group']
249
255
250 # thirds to validate uniqueness
256 # thirds to validate uniqueness
251 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
257 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
252 third.deserialize({'unique_repo_group_name': validated_name})
258 third.deserialize({'unique_repo_group_name': validated_name})
253
259
254 return appstruct
260 return appstruct
255
261
256
262
257 class RepoGroupSettingsSchema(RepoGroupSchema):
263 class RepoGroupSettingsSchema(RepoGroupSchema):
258 repo_group = colander.SchemaNode(
264 repo_group = colander.SchemaNode(
259 colander.Integer(),
265 colander.Integer(),
260 validator=deferred_repo_group_validator,
266 validator=deferred_repo_group_validator,
261 widget=deferred_repo_group_widget,
267 widget=deferred_repo_group_widget,
262 missing='')
268 missing='')
263
269
264 def deserialize(self, cstruct):
270 def deserialize(self, cstruct):
265 """
271 """
266 Custom deserialize that allows to chain validation, and verify
272 Custom deserialize that allows to chain validation, and verify
267 permissions, and as last step uniqueness
273 permissions, and as last step uniqueness
268 """
274 """
269
275
270 # first pass, to validate given data
276 # first pass, to validate given data
271 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
277 appstruct = super(RepoGroupSchema, self).deserialize(cstruct)
272 validated_name = appstruct['repo_group_name']
278 validated_name = appstruct['repo_group_name']
273
279
280 # because of repoSchema adds repo-group as an ID, we inject it as
281 # full name here because validators require it, it's unwrapped later
282 # so it's safe to use and final name is going to be without group anyway
283
284 group, separator = get_repo_group(appstruct['repo_group'])
285 if group:
286 validated_name = separator.join([group.group_name, validated_name])
287
274 # second pass to validate permissions to repo_group
288 # second pass to validate permissions to repo_group
275 second = RepoGroupAccessSchema().bind(**self.bindings)
289 second = RepoGroupAccessSchema().bind(**self.bindings)
276 appstruct_second = second.deserialize({'repo_group': validated_name})
290 appstruct_second = second.deserialize({'repo_group': validated_name})
277 # save result
291 # save result
278 appstruct['repo_group'] = appstruct_second['repo_group']
292 appstruct['repo_group'] = appstruct_second['repo_group']
279
293
280 # thirds to validate uniqueness
294 # thirds to validate uniqueness
281 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
295 third = RepoGroupNameUniqueSchema().bind(**self.bindings)
282 third.deserialize({'unique_repo_group_name': validated_name})
296 third.deserialize({'unique_repo_group_name': validated_name})
283
297
284 return appstruct
298 return appstruct
@@ -1,116 +1,118 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 pytest
22 import pytest
23
23
24 from rhodecode.model.validation_schema import types
24 from rhodecode.model.validation_schema import types
25 from rhodecode.model.validation_schema.schemas import repo_group_schema
25 from rhodecode.model.validation_schema.schemas import repo_group_schema
26
26
27
27
28 class TestRepoGroupSchema(object):
28 class TestRepoGroupSchema(object):
29
29
30 @pytest.mark.parametrize('given, expected', [
30 @pytest.mark.parametrize('given, expected', [
31 ('my repo', 'my-repo'),
31 ('my repo', 'my-repo'),
32 (' hello world mike ', 'hello-world-mike'),
32 (' hello world mike ', 'hello-world-mike'),
33
33
34 ('//group1/group2//', 'group1/group2'),
34 ('//group1/group2//', 'group1/group2'),
35 ('//group1///group2//', 'group1/group2'),
35 ('//group1///group2//', 'group1/group2'),
36 ('///group1/group2///group3', 'group1/group2/group3'),
36 ('///group1/group2///group3', 'group1/group2/group3'),
37 ('word g1/group2///group3', 'word-g1/group2/group3'),
37 ('word g1/group2///group3', 'word-g1/group2/group3'),
38
38
39 ('grou p1/gro;,,##up2//.../group3', 'grou-p1/group2/group3'),
39 ('grou p1/gro;,,##up2//.../group3', 'grou-p1/group2/group3'),
40
40
41 ('group,,,/,,,/1/2/3', 'group/1/2/3'),
41 ('group,,,/,,,/1/2/3', 'group/1/2/3'),
42 ('grou[]p1/gro;up2///gro up3', 'group1/group2/gro-up3'),
42 ('grou[]p1/gro;up2///gro up3', 'group1/group2/gro-up3'),
43 (u'grou[]p1/gro;up2///gro up3/Δ…Δ‡', u'group1/group2/gro-up3/Δ…Δ‡'),
43 (u'grou[]p1/gro;up2///gro up3/Δ…Δ‡', u'group1/group2/gro-up3/Δ…Δ‡'),
44 ])
44 ])
45 def test_deserialize_repo_name(self, app, user_admin, given, expected):
45 def test_deserialize_repo_name(self, app, user_admin, given, expected):
46 schema = repo_group_schema.RepoGroupSchema().bind()
46 schema = repo_group_schema.RepoGroupSchema().bind()
47 assert schema.get('repo_group_name').deserialize(given) == expected
47 assert schema.get('repo_group_name').deserialize(given) == expected
48
48
49 def test_deserialize(self, app, user_admin):
49 def test_deserialize(self, app, user_admin):
50 schema = repo_group_schema.RepoGroupSchema().bind(
50 schema = repo_group_schema.RepoGroupSchema().bind(
51 user=user_admin
51 user=user_admin
52 )
52 )
53
53
54 schema_data = schema.deserialize(dict(
54 schema_data = schema.deserialize(dict(
55 repo_group_name='my_schema_group',
55 repo_group_name='my_schema_group',
56 repo_group_owner=user_admin.username
56 repo_group_owner=user_admin.username
57 ))
57 ))
58
58
59 assert schema_data['repo_group_name'] == u'my_schema_group'
59 assert schema_data['repo_group_name'] == u'my_schema_group'
60 assert schema_data['repo_group'] == {
60 assert schema_data['repo_group'] == {
61 'repo_group_id': None,
61 'repo_group_id': None,
62 'repo_group_name': types.RootLocation,
62 'repo_group_name': types.RootLocation,
63 'repo_group_name_with_group': u'my_schema_group',
63 'repo_group_name_without_group': u'my_schema_group'}
64 'repo_group_name_without_group': u'my_schema_group'}
64
65
65 @pytest.mark.parametrize('given, err_key, expected_exc', [
66 @pytest.mark.parametrize('given, err_key, expected_exc', [
66 ('xxx/my_schema_group', 'repo_group', 'Parent repository group `xxx` does not exist'),
67 ('xxx/my_schema_group', 'repo_group', 'Parent repository group `xxx` does not exist'),
67 ('', 'repo_group_name', 'Name must start with a letter or number. Got ``'),
68 ('', 'repo_group_name', 'Name must start with a letter or number. Got ``'),
68 ])
69 ])
69 def test_deserialize_with_bad_group_name(
70 def test_deserialize_with_bad_group_name(
70 self, app, user_admin, given, err_key, expected_exc):
71 self, app, user_admin, given, err_key, expected_exc):
71 schema = repo_group_schema.RepoGroupSchema().bind(
72 schema = repo_group_schema.RepoGroupSchema().bind(
72 repo_type_options=['hg'],
73 repo_type_options=['hg'],
73 user=user_admin
74 user=user_admin
74 )
75 )
75
76
76 with pytest.raises(colander.Invalid) as excinfo:
77 with pytest.raises(colander.Invalid) as excinfo:
77 schema.deserialize(dict(
78 schema.deserialize(dict(
78 repo_group_name=given,
79 repo_group_name=given,
79 repo_group_owner=user_admin.username
80 repo_group_owner=user_admin.username
80 ))
81 ))
81
82
82 assert excinfo.value.asdict()[err_key] == expected_exc
83 assert excinfo.value.asdict()[err_key] == expected_exc
83
84
84 def test_deserialize_with_group_name(self, app, user_admin, test_repo_group):
85 def test_deserialize_with_group_name(self, app, user_admin, test_repo_group):
85 schema = repo_group_schema.RepoGroupSchema().bind(
86 schema = repo_group_schema.RepoGroupSchema().bind(
86 user=user_admin
87 user=user_admin
87 )
88 )
88
89
89 full_name = test_repo_group.group_name + u'/my_schema_group'
90 full_name = test_repo_group.group_name + u'/my_schema_group'
90 schema_data = schema.deserialize(dict(
91 schema_data = schema.deserialize(dict(
91 repo_group_name=full_name,
92 repo_group_name=full_name,
92 repo_group_owner=user_admin.username
93 repo_group_owner=user_admin.username
93 ))
94 ))
94
95
95 assert schema_data['repo_group_name'] == full_name
96 assert schema_data['repo_group_name'] == full_name
96 assert schema_data['repo_group'] == {
97 assert schema_data['repo_group'] == {
97 'repo_group_id': test_repo_group.group_id,
98 'repo_group_id': test_repo_group.group_id,
98 'repo_group_name': test_repo_group.group_name,
99 'repo_group_name': test_repo_group.group_name,
100 'repo_group_name_with_group': full_name,
99 'repo_group_name_without_group': u'my_schema_group'}
101 'repo_group_name_without_group': u'my_schema_group'}
100
102
101 def test_deserialize_with_group_name_regular_user_no_perms(
103 def test_deserialize_with_group_name_regular_user_no_perms(
102 self, app, user_regular, test_repo_group):
104 self, app, user_regular, test_repo_group):
103 schema = repo_group_schema.RepoGroupSchema().bind(
105 schema = repo_group_schema.RepoGroupSchema().bind(
104 user=user_regular
106 user=user_regular
105 )
107 )
106
108
107 full_name = test_repo_group.group_name + u'/my_schema_group'
109 full_name = test_repo_group.group_name + u'/my_schema_group'
108 with pytest.raises(colander.Invalid) as excinfo:
110 with pytest.raises(colander.Invalid) as excinfo:
109 schema.deserialize(dict(
111 schema.deserialize(dict(
110 repo_group_name=full_name,
112 repo_group_name=full_name,
111 repo_group_owner=user_regular.username
113 repo_group_owner=user_regular.username
112 ))
114 ))
113
115
114 expected = 'Parent repository group `{}` does not exist'.format(
116 expected = 'Parent repository group `{}` does not exist'.format(
115 test_repo_group.group_name)
117 test_repo_group.group_name)
116 assert excinfo.value.asdict()['repo_group'] == expected
118 assert excinfo.value.asdict()['repo_group'] == expected
General Comments 0
You need to be logged in to leave comments. Login now