##// END OF EJS Templates
repos, repo groups, user groups: allow to use disabled users in owner field....
marcink -
r224:c6a3436d default
parent child Browse files
Show More
@@ -1,407 +1,406 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repository groups controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import formencode
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _, ungettext
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, NotAnonymous, HasPermissionAll,
40 40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.model.db import RepoGroup, User
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.repo_group import RepoGroupModel
45 45 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.lib.utils2 import safe_int
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class RepoGroupsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55
56 56 @LoginRequired()
57 57 def __before__(self):
58 58 super(RepoGroupsController, self).__before__()
59 59
60 60 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 61 if self._can_create_repo_group():
62 62 # we're global admin, we're ok and we can create TOP level groups
63 63 allow_empty_group = True
64 64
65 65 # override the choices for this form, we need to filter choices
66 66 # and display only those we have ADMIN right
67 67 groups_with_admin_rights = RepoGroupList(
68 68 RepoGroup.query().all(),
69 69 perm_set=['group.admin'])
70 70 c.repo_groups = RepoGroup.groups_choices(
71 71 groups=groups_with_admin_rights,
72 72 show_empty_group=allow_empty_group)
73 73
74 74 if repo_group:
75 75 # exclude filtered ids
76 76 exclude_group_ids = [repo_group.group_id]
77 77 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 78 c.repo_groups)
79 79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 80 parent_group = repo_group.parent_group
81 81
82 82 add_parent_group = (parent_group and (
83 83 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 84 if add_parent_group:
85 85 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 86 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87 87
88 88 def __load_data(self, group_id):
89 89 """
90 90 Load defaults settings for edit, and update
91 91
92 92 :param group_id:
93 93 """
94 94 repo_group = RepoGroup.get_or_404(group_id)
95 95 data = repo_group.get_dict()
96 96 data['group_name'] = repo_group.name
97 97
98 98 # fill owner
99 99 if repo_group.user:
100 100 data.update({'user': repo_group.user.username})
101 101 else:
102 102 replacement_user = User.get_first_admin().username
103 103 data.update({'user': replacement_user})
104 104
105 105 # fill repository group users
106 106 for p in repo_group.repo_group_to_perm:
107 107 data.update({
108 108 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109 109
110 110 # fill repository group user groups
111 111 for p in repo_group.users_group_to_perm:
112 112 data.update({
113 113 'g_perm_%s' % p.users_group.users_group_id:
114 114 p.permission.permission_name})
115 115 # html and form expects -1 as empty parent group
116 116 data['group_parent_id'] = data['group_parent_id'] or -1
117 117 return data
118 118
119 119 def _revoke_perms_on_yourself(self, form_result):
120 120 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 121 form_result['perm_updates'])
122 122 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 123 form_result['perm_additions'])
124 124 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 125 form_result['perm_deletions'])
126 126 admin_perm = 'group.admin'
127 127 if _updates and _updates[0][1] != admin_perm or \
128 128 _additions and _additions[0][1] != admin_perm or \
129 129 _deletions and _deletions[0][1] != admin_perm:
130 130 return True
131 131 return False
132 132
133 133 def _can_create_repo_group(self, parent_group_id=None):
134 134 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 135 create_repo_group = HasPermissionAll(
136 136 'hg.repogroup.create.true')('group create controller')
137 137 if is_admin or (create_repo_group and not parent_group_id):
138 138 # we're global admin, or we have global repo group create
139 139 # permission
140 140 # we're ok and we can create TOP level groups
141 141 return True
142 142 elif parent_group_id:
143 143 # we check the permission if we can write to parent group
144 144 group = RepoGroup.get(parent_group_id)
145 145 group_name = group.group_name if group else None
146 146 if HasRepoGroupPermissionAll('group.admin')(
147 147 group_name, 'check if user is an admin of group'):
148 148 # we're an admin of passed in group, we're ok.
149 149 return True
150 150 else:
151 151 return False
152 152 return False
153 153
154 154 @NotAnonymous()
155 155 def index(self):
156 156 """GET /repo_groups: All items in the collection"""
157 157 # url('repo_groups')
158 158
159 159 repo_group_list = RepoGroup.get_all_repo_groups()
160 160 _perms = ['group.admin']
161 161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
162 162 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
163 163 repo_group_list=repo_group_list_acl, admin=True)
164 164 c.data = json.dumps(repo_group_data)
165 165 return render('admin/repo_groups/repo_groups.html')
166 166
167 167 # perm checks inside
168 168 @NotAnonymous()
169 169 @auth.CSRFRequired()
170 170 def create(self):
171 171 """POST /repo_groups: Create a new item"""
172 172 # url('repo_groups')
173 173
174 174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 175 can_create = self._can_create_repo_group(parent_group_id)
176 176
177 177 self.__load_defaults()
178 178 # permissions for can create group based on parent_id are checked
179 179 # here in the Form
180 180 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
181 181 repo_group_form = RepoGroupForm(available_groups=available_groups,
182 182 can_create_in_root=can_create)()
183 183 try:
184 184 owner = c.rhodecode_user
185 185 form_result = repo_group_form.to_python(dict(request.POST))
186 186 RepoGroupModel().create(
187 187 group_name=form_result['group_name_full'],
188 188 group_description=form_result['group_description'],
189 189 owner=owner.user_id,
190 190 copy_permissions=form_result['group_copy_permissions']
191 191 )
192 192 Session().commit()
193 193 _new_group_name = form_result['group_name_full']
194 194 repo_group_url = h.link_to(
195 195 _new_group_name,
196 196 h.url('repo_group_home', group_name=_new_group_name))
197 197 h.flash(h.literal(_('Created repository group %s')
198 198 % repo_group_url), category='success')
199 199 # TODO: in futureaction_logger(, '', '', '', self.sa)
200 200 except formencode.Invalid as errors:
201 201 return htmlfill.render(
202 202 render('admin/repo_groups/repo_group_add.html'),
203 203 defaults=errors.value,
204 204 errors=errors.error_dict or {},
205 205 prefix_error=False,
206 206 encoding="UTF-8",
207 207 force_defaults=False)
208 208 except Exception:
209 209 log.exception("Exception during creation of repository group")
210 210 h.flash(_('Error occurred during creation of repository group %s')
211 211 % request.POST.get('group_name'), category='error')
212 212
213 213 # TODO: maybe we should get back to the main view, not the admin one
214 214 return redirect(url('repo_groups', parent_group=parent_group_id))
215 215
216 216 # perm checks inside
217 217 @NotAnonymous()
218 218 def new(self):
219 219 """GET /repo_groups/new: Form to create a new item"""
220 220 # url('new_repo_group')
221 221 # perm check for admin, create_group perm or admin of parent_group
222 222 parent_group_id = safe_int(request.GET.get('parent_group'))
223 223 if not self._can_create_repo_group(parent_group_id):
224 224 return abort(403)
225 225
226 226 self.__load_defaults()
227 227 return render('admin/repo_groups/repo_group_add.html')
228 228
229 229 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 230 @auth.CSRFRequired()
231 231 def update(self, group_name):
232 232 """PUT /repo_groups/group_name: Update an existing item"""
233 233 # Forms posted to this method should contain a hidden field:
234 234 # <input type="hidden" name="_method" value="PUT" />
235 235 # Or using helpers:
236 236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
237 237 # url('repo_group_home', group_name=GROUP_NAME)
238 238
239 239 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
240 240 can_create_in_root = self._can_create_repo_group()
241 241 show_root_location = can_create_in_root
242 242 if not c.repo_group.parent_group:
243 243 # this group don't have a parrent so we should show empty value
244 244 show_root_location = True
245 245 self.__load_defaults(allow_empty_group=show_root_location,
246 246 repo_group=c.repo_group)
247 247
248 248 repo_group_form = RepoGroupForm(
249 edit=True,
250 old_data=c.repo_group.get_dict(),
249 edit=True, old_data=c.repo_group.get_dict(),
251 250 available_groups=c.repo_groups_choices,
252 can_create_in_root=can_create_in_root,
253 )()
251 can_create_in_root=can_create_in_root, allow_disabled=True)()
252
254 253 try:
255 254 form_result = repo_group_form.to_python(dict(request.POST))
256 255 gr_name = form_result['group_name']
257 256 new_gr = RepoGroupModel().update(group_name, form_result)
258 257 Session().commit()
259 258 h.flash(_('Updated repository group %s') % (gr_name,),
260 259 category='success')
261 260 # we now have new name !
262 261 group_name = new_gr.group_name
263 262 # TODO: in future action_logger(, '', '', '', self.sa)
264 263 except formencode.Invalid as errors:
265 264 c.active = 'settings'
266 265 return htmlfill.render(
267 266 render('admin/repo_groups/repo_group_edit.html'),
268 267 defaults=errors.value,
269 268 errors=errors.error_dict or {},
270 269 prefix_error=False,
271 270 encoding="UTF-8",
272 271 force_defaults=False)
273 272 except Exception:
274 273 log.exception("Exception during update or repository group")
275 274 h.flash(_('Error occurred during update of repository group %s')
276 275 % request.POST.get('group_name'), category='error')
277 276
278 277 return redirect(url('edit_repo_group', group_name=group_name))
279 278
280 279 @HasRepoGroupPermissionAnyDecorator('group.admin')
281 280 @auth.CSRFRequired()
282 281 def delete(self, group_name):
283 282 """DELETE /repo_groups/group_name: Delete an existing item"""
284 283 # Forms posted to this method should contain a hidden field:
285 284 # <input type="hidden" name="_method" value="DELETE" />
286 285 # Or using helpers:
287 286 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
288 287 # url('repo_group_home', group_name=GROUP_NAME)
289 288
290 289 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
291 290 repos = gr.repositories.all()
292 291 if repos:
293 292 msg = ungettext(
294 293 'This group contains %(num)d repository and cannot be deleted',
295 294 'This group contains %(num)d repositories and cannot be'
296 295 ' deleted',
297 296 len(repos)) % {'num': len(repos)}
298 297 h.flash(msg, category='warning')
299 298 return redirect(url('repo_groups'))
300 299
301 300 children = gr.children.all()
302 301 if children:
303 302 msg = ungettext(
304 303 'This group contains %(num)d subgroup and cannot be deleted',
305 304 'This group contains %(num)d subgroups and cannot be deleted',
306 305 len(children)) % {'num': len(children)}
307 306 h.flash(msg, category='warning')
308 307 return redirect(url('repo_groups'))
309 308
310 309 try:
311 310 RepoGroupModel().delete(group_name)
312 311 Session().commit()
313 312 h.flash(_('Removed repository group %s') % group_name,
314 313 category='success')
315 314 # TODO: in future action_logger(, '', '', '', self.sa)
316 315 except Exception:
317 316 log.exception("Exception during deletion of repository group")
318 317 h.flash(_('Error occurred during deletion of repository group %s')
319 318 % group_name, category='error')
320 319
321 320 return redirect(url('repo_groups'))
322 321
323 322 @HasRepoGroupPermissionAnyDecorator('group.admin')
324 323 def edit(self, group_name):
325 324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
326 325 # url('edit_repo_group', group_name=GROUP_NAME)
327 326 c.active = 'settings'
328 327
329 328 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
330 329 # we can only allow moving empty group if it's already a top-level
331 330 # group, ie has no parents, or we're admin
332 331 can_create_in_root = self._can_create_repo_group()
333 332 show_root_location = can_create_in_root
334 333 if not c.repo_group.parent_group:
335 334 # this group don't have a parrent so we should show empty value
336 335 show_root_location = True
337 336 self.__load_defaults(allow_empty_group=show_root_location,
338 337 repo_group=c.repo_group)
339 338 defaults = self.__load_data(c.repo_group.group_id)
340 339
341 340 return htmlfill.render(
342 341 render('admin/repo_groups/repo_group_edit.html'),
343 342 defaults=defaults,
344 343 encoding="UTF-8",
345 344 force_defaults=False
346 345 )
347 346
348 347 @HasRepoGroupPermissionAnyDecorator('group.admin')
349 348 def edit_repo_group_advanced(self, group_name):
350 349 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
351 350 # url('edit_repo_group', group_name=GROUP_NAME)
352 351 c.active = 'advanced'
353 352 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
354 353
355 354 return render('admin/repo_groups/repo_group_edit.html')
356 355
357 356 @HasRepoGroupPermissionAnyDecorator('group.admin')
358 357 def edit_repo_group_perms(self, group_name):
359 358 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
360 359 # url('edit_repo_group', group_name=GROUP_NAME)
361 360 c.active = 'perms'
362 361 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
363 362 self.__load_defaults()
364 363 defaults = self.__load_data(c.repo_group.group_id)
365 364
366 365 return htmlfill.render(
367 366 render('admin/repo_groups/repo_group_edit.html'),
368 367 defaults=defaults,
369 368 encoding="UTF-8",
370 369 force_defaults=False
371 370 )
372 371
373 372 @HasRepoGroupPermissionAnyDecorator('group.admin')
374 373 @auth.CSRFRequired()
375 374 def update_perms(self, group_name):
376 375 """
377 376 Update permissions for given repository group
378 377
379 378 :param group_name:
380 379 """
381 380
382 381 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
383 382 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
384 383 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
385 384 request.POST)
386 385
387 386 if not c.rhodecode_user.is_admin:
388 387 if self._revoke_perms_on_yourself(form):
389 388 msg = _('Cannot change permission for yourself as admin')
390 389 h.flash(msg, category='warning')
391 390 return redirect(
392 391 url('edit_repo_group_perms', group_name=group_name))
393 392
394 393 # iterate over all members(if in recursive mode) of this groups and
395 394 # set the permissions !
396 395 # this can be potentially heavy operation
397 396 RepoGroupModel().update_permissions(
398 397 c.repo_group,
399 398 form['perm_additions'], form['perm_updates'],
400 399 form['perm_deletions'], form['recursive'])
401 400
402 401 # TODO: implement this
403 402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
404 403 # repo_name, self.ip_addr, self.sa)
405 404 Session().commit()
406 405 h.flash(_('Repository Group permissions updated'), category='success')
407 406 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,878 +1,878 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 from rhodecode.lib import auth, helpers as h
37 37 from rhodecode.lib.auth import (
38 38 LoginRequired, HasPermissionAllDecorator,
39 39 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
40 40 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
41 41 from rhodecode.lib.base import BaseRepoController, render
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.exceptions import AttachedForksError
44 44 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
45 45 from rhodecode.lib.utils2 import safe_int
46 46 from rhodecode.lib.vcs import RepositoryError
47 47 from rhodecode.model.db import (
48 48 User, Repository, UserFollowing, RepoGroup, RepositoryField)
49 49 from rhodecode.model.forms import (
50 50 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
51 51 IssueTrackerPatternsForm)
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
55 55 from rhodecode.model.settings import (
56 56 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
57 57 SettingNotFound)
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class ReposController(BaseRepoController):
63 63 """
64 64 REST Controller styled on the Atom Publishing Protocol"""
65 65 # To properly map this controller, ensure your config/routing.py
66 66 # file has a resource setup:
67 67 # map.resource('repo', 'repos')
68 68
69 69 @LoginRequired()
70 70 def __before__(self):
71 71 super(ReposController, self).__before__()
72 72
73 73 def _load_repo(self, repo_name):
74 74 repo_obj = Repository.get_by_repo_name(repo_name)
75 75
76 76 if repo_obj is None:
77 77 h.not_mapped_error(repo_name)
78 78 return redirect(url('repos'))
79 79
80 80 return repo_obj
81 81
82 82 def __load_defaults(self, repo=None):
83 83 acl_groups = RepoGroupList(RepoGroup.query().all(),
84 84 perm_set=['group.write', 'group.admin'])
85 85 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
86 86 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
87 87
88 88 # in case someone no longer have a group.write access to a repository
89 89 # pre fill the list with this entry, we don't care if this is the same
90 90 # but it will allow saving repo data properly.
91 91
92 92 repo_group = None
93 93 if repo:
94 94 repo_group = repo.group
95 95 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
96 96 c.repo_groups_choices.append(unicode(repo_group.group_id))
97 97 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
98 98
99 99 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
100 100 c.landing_revs_choices = choices
101 101
102 102 def __load_data(self, repo_name=None):
103 103 """
104 104 Load defaults settings for edit, and update
105 105
106 106 :param repo_name:
107 107 """
108 108 c.repo_info = self._load_repo(repo_name)
109 109 self.__load_defaults(c.repo_info)
110 110
111 111 # override defaults for exact repo info here git/hg etc
112 112 if not c.repository_requirements_missing:
113 113 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
114 114 c.repo_info)
115 115 c.landing_revs_choices = choices
116 116 defaults = RepoModel()._get_defaults(repo_name)
117 117
118 118 return defaults
119 119
120 120 def _log_creation_exception(self, e, repo_name):
121 121 reason = None
122 122 if len(e.args) == 2:
123 123 reason = e.args[1]
124 124
125 125 if reason == 'INVALID_CERTIFICATE':
126 126 log.exception(
127 127 'Exception creating a repository: invalid certificate')
128 128 msg = (_('Error creating repository %s: invalid certificate')
129 129 % repo_name)
130 130 else:
131 131 log.exception("Exception creating a repository")
132 132 msg = (_('Error creating repository %s')
133 133 % repo_name)
134 134
135 135 return msg
136 136
137 137 @NotAnonymous()
138 138 def index(self, format='html'):
139 139 """GET /repos: All items in the collection"""
140 140 # url('repos')
141 141
142 142 repo_list = Repository.get_all_repos()
143 143 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
144 144 repos_data = RepoModel().get_repos_as_dict(
145 145 repo_list=c.repo_list, admin=True, super_user_actions=True)
146 146 # json used to render the grid
147 147 c.data = json.dumps(repos_data)
148 148
149 149 return render('admin/repos/repos.html')
150 150
151 151 # perms check inside
152 152 @NotAnonymous()
153 153 @auth.CSRFRequired()
154 154 def create(self):
155 155 """
156 156 POST /repos: Create a new item"""
157 157 # url('repos')
158 158
159 159 self.__load_defaults()
160 160 form_result = {}
161 161 task_id = None
162 162 try:
163 163 # CanWriteToGroup validators checks permissions of this POST
164 164 form_result = RepoForm(repo_groups=c.repo_groups_choices,
165 165 landing_revs=c.landing_revs_choices)()\
166 166 .to_python(dict(request.POST))
167 167
168 168 # create is done sometimes async on celery, db transaction
169 169 # management is handled there.
170 170 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
171 171 from celery.result import BaseAsyncResult
172 172 if isinstance(task, BaseAsyncResult):
173 173 task_id = task.task_id
174 174 except formencode.Invalid as errors:
175 175 c.personal_repo_group = RepoGroup.get_by_group_name(
176 176 c.rhodecode_user.username)
177 177 return htmlfill.render(
178 178 render('admin/repos/repo_add.html'),
179 179 defaults=errors.value,
180 180 errors=errors.error_dict or {},
181 181 prefix_error=False,
182 182 encoding="UTF-8",
183 183 force_defaults=False)
184 184
185 185 except Exception as e:
186 186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 187 h.flash(msg, category='error')
188 188 return redirect(url('home'))
189 189
190 190 return redirect(h.url('repo_creating_home',
191 191 repo_name=form_result['repo_name_full'],
192 192 task_id=task_id))
193 193
194 194 # perms check inside
195 195 @NotAnonymous()
196 196 def create_repository(self):
197 197 """GET /_admin/create_repository: Form to create a new item"""
198 198 new_repo = request.GET.get('repo', '')
199 199 parent_group = request.GET.get('parent_group')
200 200 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
201 201 # you're not super admin nor have global create permissions,
202 202 # but maybe you have at least write permission to a parent group ?
203 203 _gr = RepoGroup.get(parent_group)
204 204 gr_name = _gr.group_name if _gr else None
205 205 # create repositories with write permission on group is set to true
206 206 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
207 207 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
208 208 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
209 209 if not (group_admin or (group_write and create_on_write)):
210 210 raise HTTPForbidden
211 211
212 212 acl_groups = RepoGroupList(RepoGroup.query().all(),
213 213 perm_set=['group.write', 'group.admin'])
214 214 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
215 215 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
216 216 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
217 217 c.personal_repo_group = RepoGroup.get_by_group_name(c.rhodecode_user.username)
218 218 c.new_repo = repo_name_slug(new_repo)
219 219
220 220 ## apply the defaults from defaults page
221 221 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
222 222 # set checkbox to autochecked
223 223 defaults['repo_copy_permissions'] = True
224 224 if parent_group:
225 225 defaults.update({'repo_group': parent_group})
226 226
227 227 return htmlfill.render(
228 228 render('admin/repos/repo_add.html'),
229 229 defaults=defaults,
230 230 errors={},
231 231 prefix_error=False,
232 232 encoding="UTF-8",
233 233 force_defaults=False
234 234 )
235 235
236 236 @NotAnonymous()
237 237 def repo_creating(self, repo_name):
238 238 c.repo = repo_name
239 239 c.task_id = request.GET.get('task_id')
240 240 if not c.repo:
241 241 raise HTTPNotFound()
242 242 return render('admin/repos/repo_creating.html')
243 243
244 244 @NotAnonymous()
245 245 @jsonify
246 246 def repo_check(self, repo_name):
247 247 c.repo = repo_name
248 248 task_id = request.GET.get('task_id')
249 249
250 250 if task_id and task_id not in ['None']:
251 251 from rhodecode import CELERY_ENABLED
252 252 from celery.result import AsyncResult
253 253 if CELERY_ENABLED:
254 254 task = AsyncResult(task_id)
255 255 if task.failed():
256 256 msg = self._log_creation_exception(task.result, c.repo)
257 257 h.flash(msg, category='error')
258 258 return redirect(url('home'), code=501)
259 259
260 260 repo = Repository.get_by_repo_name(repo_name)
261 261 if repo and repo.repo_state == Repository.STATE_CREATED:
262 262 if repo.clone_uri:
263 263 clone_uri = repo.clone_uri_hidden
264 264 h.flash(_('Created repository %s from %s')
265 265 % (repo.repo_name, clone_uri), category='success')
266 266 else:
267 267 repo_url = h.link_to(repo.repo_name,
268 268 h.url('summary_home',
269 269 repo_name=repo.repo_name))
270 270 fork = repo.fork
271 271 if fork:
272 272 fork_name = fork.repo_name
273 273 h.flash(h.literal(_('Forked repository %s as %s')
274 274 % (fork_name, repo_url)), category='success')
275 275 else:
276 276 h.flash(h.literal(_('Created repository %s') % repo_url),
277 277 category='success')
278 278 return {'result': True}
279 279 return {'result': False}
280 280
281 281 @HasRepoPermissionAllDecorator('repository.admin')
282 282 @auth.CSRFRequired()
283 283 def update(self, repo_name):
284 284 """
285 285 PUT /repos/repo_name: Update an existing item"""
286 286 # Forms posted to this method should contain a hidden field:
287 287 # <input type="hidden" name="_method" value="PUT" />
288 288 # Or using helpers:
289 289 # h.form(url('repo', repo_name=ID),
290 290 # method='put')
291 291 # url('repo', repo_name=ID)
292 292
293 293 self.__load_data(repo_name)
294 294 c.active = 'settings'
295 295 c.repo_fields = RepositoryField.query()\
296 296 .filter(RepositoryField.repository == c.repo_info).all()
297 297
298 298 repo_model = RepoModel()
299 299 changed_name = repo_name
300 300
301 301 # override the choices with extracted revisions !
302 302 c.personal_repo_group = RepoGroup.get_by_group_name(
303 303 c.rhodecode_user.username)
304 304 repo = Repository.get_by_repo_name(repo_name)
305 305 old_data = {
306 306 'repo_name': repo_name,
307 307 'repo_group': repo.group.get_dict() if repo.group else {},
308 308 'repo_type': repo.repo_type,
309 309 }
310 _form = RepoForm(edit=True, old_data=old_data,
311 repo_groups=c.repo_groups_choices,
312 landing_revs=c.landing_revs_choices)()
310 _form = RepoForm(
311 edit=True, old_data=old_data, repo_groups=c.repo_groups_choices,
312 landing_revs=c.landing_revs_choices, allow_disabled=True)()
313 313
314 314 try:
315 315 form_result = _form.to_python(dict(request.POST))
316 316 repo = repo_model.update(repo_name, **form_result)
317 317 ScmModel().mark_for_invalidation(repo_name)
318 318 h.flash(_('Repository %s updated successfully') % repo_name,
319 319 category='success')
320 320 changed_name = repo.repo_name
321 321 action_logger(c.rhodecode_user, 'admin_updated_repo',
322 322 changed_name, self.ip_addr, self.sa)
323 323 Session().commit()
324 324 except formencode.Invalid as errors:
325 325 defaults = self.__load_data(repo_name)
326 326 defaults.update(errors.value)
327 327 return htmlfill.render(
328 328 render('admin/repos/repo_edit.html'),
329 329 defaults=defaults,
330 330 errors=errors.error_dict or {},
331 331 prefix_error=False,
332 332 encoding="UTF-8",
333 333 force_defaults=False)
334 334
335 335 except Exception:
336 336 log.exception("Exception during update of repository")
337 337 h.flash(_('Error occurred during update of repository %s') \
338 338 % repo_name, category='error')
339 339 return redirect(url('edit_repo', repo_name=changed_name))
340 340
341 341 @HasRepoPermissionAllDecorator('repository.admin')
342 342 @auth.CSRFRequired()
343 343 def delete(self, repo_name):
344 344 """
345 345 DELETE /repos/repo_name: Delete an existing item"""
346 346 # Forms posted to this method should contain a hidden field:
347 347 # <input type="hidden" name="_method" value="DELETE" />
348 348 # Or using helpers:
349 349 # h.form(url('repo', repo_name=ID),
350 350 # method='delete')
351 351 # url('repo', repo_name=ID)
352 352
353 353 repo_model = RepoModel()
354 354 repo = repo_model.get_by_repo_name(repo_name)
355 355 if not repo:
356 356 h.not_mapped_error(repo_name)
357 357 return redirect(url('repos'))
358 358 try:
359 359 _forks = repo.forks.count()
360 360 handle_forks = None
361 361 if _forks and request.POST.get('forks'):
362 362 do = request.POST['forks']
363 363 if do == 'detach_forks':
364 364 handle_forks = 'detach'
365 365 h.flash(_('Detached %s forks') % _forks, category='success')
366 366 elif do == 'delete_forks':
367 367 handle_forks = 'delete'
368 368 h.flash(_('Deleted %s forks') % _forks, category='success')
369 369 repo_model.delete(repo, forks=handle_forks)
370 370 action_logger(c.rhodecode_user, 'admin_deleted_repo',
371 371 repo_name, self.ip_addr, self.sa)
372 372 ScmModel().mark_for_invalidation(repo_name)
373 373 h.flash(_('Deleted repository %s') % repo_name, category='success')
374 374 Session().commit()
375 375 except AttachedForksError:
376 376 h.flash(_('Cannot delete %s it still contains attached forks')
377 377 % repo_name, category='warning')
378 378
379 379 except Exception:
380 380 log.exception("Exception during deletion of repository")
381 381 h.flash(_('An error occurred during deletion of %s') % repo_name,
382 382 category='error')
383 383
384 384 return redirect(url('repos'))
385 385
386 386 @HasPermissionAllDecorator('hg.admin')
387 387 def show(self, repo_name, format='html'):
388 388 """GET /repos/repo_name: Show a specific item"""
389 389 # url('repo', repo_name=ID)
390 390
391 391 @HasRepoPermissionAllDecorator('repository.admin')
392 392 def edit(self, repo_name):
393 393 """GET /repo_name/settings: Form to edit an existing item"""
394 394 # url('edit_repo', repo_name=ID)
395 395 defaults = self.__load_data(repo_name)
396 396 if 'clone_uri' in defaults:
397 397 del defaults['clone_uri']
398 398
399 399 c.repo_fields = RepositoryField.query()\
400 400 .filter(RepositoryField.repository == c.repo_info).all()
401 401 c.personal_repo_group = RepoGroup.get_by_group_name(
402 402 c.rhodecode_user.username)
403 403 c.active = 'settings'
404 404 return htmlfill.render(
405 405 render('admin/repos/repo_edit.html'),
406 406 defaults=defaults,
407 407 encoding="UTF-8",
408 408 force_defaults=False)
409 409
410 410 @HasRepoPermissionAllDecorator('repository.admin')
411 411 def edit_permissions(self, repo_name):
412 412 """GET /repo_name/settings: Form to edit an existing item"""
413 413 # url('edit_repo', repo_name=ID)
414 414 c.repo_info = self._load_repo(repo_name)
415 415 c.active = 'permissions'
416 416 defaults = RepoModel()._get_defaults(repo_name)
417 417
418 418 return htmlfill.render(
419 419 render('admin/repos/repo_edit.html'),
420 420 defaults=defaults,
421 421 encoding="UTF-8",
422 422 force_defaults=False)
423 423
424 424 @HasRepoPermissionAllDecorator('repository.admin')
425 425 @auth.CSRFRequired()
426 426 def edit_permissions_update(self, repo_name):
427 427 form = RepoPermsForm()().to_python(request.POST)
428 428 RepoModel().update_permissions(repo_name,
429 429 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
430 430
431 431 #TODO: implement this
432 432 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
433 433 # repo_name, self.ip_addr, self.sa)
434 434 Session().commit()
435 435 h.flash(_('Repository permissions updated'), category='success')
436 436 return redirect(url('edit_repo_perms', repo_name=repo_name))
437 437
438 438 @HasRepoPermissionAllDecorator('repository.admin')
439 439 def edit_fields(self, repo_name):
440 440 """GET /repo_name/settings: Form to edit an existing item"""
441 441 # url('edit_repo', repo_name=ID)
442 442 c.repo_info = self._load_repo(repo_name)
443 443 c.repo_fields = RepositoryField.query()\
444 444 .filter(RepositoryField.repository == c.repo_info).all()
445 445 c.active = 'fields'
446 446 if request.POST:
447 447
448 448 return redirect(url('repo_edit_fields'))
449 449 return render('admin/repos/repo_edit.html')
450 450
451 451 @HasRepoPermissionAllDecorator('repository.admin')
452 452 @auth.CSRFRequired()
453 453 def create_repo_field(self, repo_name):
454 454 try:
455 455 form_result = RepoFieldForm()().to_python(dict(request.POST))
456 456 RepoModel().add_repo_field(
457 457 repo_name, form_result['new_field_key'],
458 458 field_type=form_result['new_field_type'],
459 459 field_value=form_result['new_field_value'],
460 460 field_label=form_result['new_field_label'],
461 461 field_desc=form_result['new_field_desc'])
462 462
463 463 Session().commit()
464 464 except Exception as e:
465 465 log.exception("Exception creating field")
466 466 msg = _('An error occurred during creation of field')
467 467 if isinstance(e, formencode.Invalid):
468 468 msg += ". " + e.msg
469 469 h.flash(msg, category='error')
470 470 return redirect(url('edit_repo_fields', repo_name=repo_name))
471 471
472 472 @HasRepoPermissionAllDecorator('repository.admin')
473 473 @auth.CSRFRequired()
474 474 def delete_repo_field(self, repo_name, field_id):
475 475 field = RepositoryField.get_or_404(field_id)
476 476 try:
477 477 RepoModel().delete_repo_field(repo_name, field.field_key)
478 478 Session().commit()
479 479 except Exception as e:
480 480 log.exception("Exception during removal of field")
481 481 msg = _('An error occurred during removal of field')
482 482 h.flash(msg, category='error')
483 483 return redirect(url('edit_repo_fields', repo_name=repo_name))
484 484
485 485 @HasRepoPermissionAllDecorator('repository.admin')
486 486 def edit_advanced(self, repo_name):
487 487 """GET /repo_name/settings: Form to edit an existing item"""
488 488 # url('edit_repo', repo_name=ID)
489 489 c.repo_info = self._load_repo(repo_name)
490 490 c.default_user_id = User.get_default_user().user_id
491 491 c.in_public_journal = UserFollowing.query()\
492 492 .filter(UserFollowing.user_id == c.default_user_id)\
493 493 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
494 494
495 495 c.active = 'advanced'
496 496 c.has_origin_repo_read_perm = False
497 497 if c.repo_info.fork:
498 498 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
499 499 'repository.write', 'repository.read', 'repository.admin')(
500 500 c.repo_info.fork.repo_name, 'repo set as fork page')
501 501
502 502 if request.POST:
503 503 return redirect(url('repo_edit_advanced'))
504 504 return render('admin/repos/repo_edit.html')
505 505
506 506 @HasRepoPermissionAllDecorator('repository.admin')
507 507 @auth.CSRFRequired()
508 508 def edit_advanced_journal(self, repo_name):
509 509 """
510 510 Set's this repository to be visible in public journal,
511 511 in other words assing default user to follow this repo
512 512
513 513 :param repo_name:
514 514 """
515 515
516 516 try:
517 517 repo_id = Repository.get_by_repo_name(repo_name).repo_id
518 518 user_id = User.get_default_user().user_id
519 519 self.scm_model.toggle_following_repo(repo_id, user_id)
520 520 h.flash(_('Updated repository visibility in public journal'),
521 521 category='success')
522 522 Session().commit()
523 523 except Exception:
524 524 h.flash(_('An error occurred during setting this'
525 525 ' repository in public journal'),
526 526 category='error')
527 527
528 528 return redirect(url('edit_repo_advanced', repo_name=repo_name))
529 529
530 530 @HasRepoPermissionAllDecorator('repository.admin')
531 531 @auth.CSRFRequired()
532 532 def edit_advanced_fork(self, repo_name):
533 533 """
534 534 Mark given repository as a fork of another
535 535
536 536 :param repo_name:
537 537 """
538 538
539 539 new_fork_id = request.POST.get('id_fork_of')
540 540 try:
541 541
542 542 if new_fork_id and not new_fork_id.isdigit():
543 543 log.error('Given fork id %s is not an INT', new_fork_id)
544 544
545 545 fork_id = safe_int(new_fork_id)
546 546 repo = ScmModel().mark_as_fork(repo_name, fork_id,
547 547 c.rhodecode_user.username)
548 548 fork = repo.fork.repo_name if repo.fork else _('Nothing')
549 549 Session().commit()
550 550 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
551 551 category='success')
552 552 except RepositoryError as e:
553 553 log.exception("Repository Error occurred")
554 554 h.flash(str(e), category='error')
555 555 except Exception as e:
556 556 log.exception("Exception while editing fork")
557 557 h.flash(_('An error occurred during this operation'),
558 558 category='error')
559 559
560 560 return redirect(url('edit_repo_advanced', repo_name=repo_name))
561 561
562 562 @HasRepoPermissionAllDecorator('repository.admin')
563 563 @auth.CSRFRequired()
564 564 def edit_advanced_locking(self, repo_name):
565 565 """
566 566 Unlock repository when it is locked !
567 567
568 568 :param repo_name:
569 569 """
570 570 try:
571 571 repo = Repository.get_by_repo_name(repo_name)
572 572 if request.POST.get('set_lock'):
573 573 Repository.lock(repo, c.rhodecode_user.user_id,
574 574 lock_reason=Repository.LOCK_WEB)
575 575 h.flash(_('Locked repository'), category='success')
576 576 elif request.POST.get('set_unlock'):
577 577 Repository.unlock(repo)
578 578 h.flash(_('Unlocked repository'), category='success')
579 579 except Exception as e:
580 580 log.exception("Exception during unlocking")
581 581 h.flash(_('An error occurred during unlocking'),
582 582 category='error')
583 583 return redirect(url('edit_repo_advanced', repo_name=repo_name))
584 584
585 585 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
586 586 @auth.CSRFRequired()
587 587 def toggle_locking(self, repo_name):
588 588 """
589 589 Toggle locking of repository by simple GET call to url
590 590
591 591 :param repo_name:
592 592 """
593 593
594 594 try:
595 595 repo = Repository.get_by_repo_name(repo_name)
596 596
597 597 if repo.enable_locking:
598 598 if repo.locked[0]:
599 599 Repository.unlock(repo)
600 600 action = _('Unlocked')
601 601 else:
602 602 Repository.lock(repo, c.rhodecode_user.user_id,
603 603 lock_reason=Repository.LOCK_WEB)
604 604 action = _('Locked')
605 605
606 606 h.flash(_('Repository has been %s') % action,
607 607 category='success')
608 608 except Exception:
609 609 log.exception("Exception during unlocking")
610 610 h.flash(_('An error occurred during unlocking'),
611 611 category='error')
612 612 return redirect(url('summary_home', repo_name=repo_name))
613 613
614 614 @HasRepoPermissionAllDecorator('repository.admin')
615 615 @auth.CSRFRequired()
616 616 def edit_caches(self, repo_name):
617 617 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
618 618 try:
619 619 ScmModel().mark_for_invalidation(repo_name, delete=True)
620 620 Session().commit()
621 621 h.flash(_('Cache invalidation successful'),
622 622 category='success')
623 623 except Exception:
624 624 log.exception("Exception during cache invalidation")
625 625 h.flash(_('An error occurred during cache invalidation'),
626 626 category='error')
627 627
628 628 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
629 629
630 630 @HasRepoPermissionAllDecorator('repository.admin')
631 631 def edit_caches_form(self, repo_name):
632 632 """GET /repo_name/settings: Form to edit an existing item"""
633 633 # url('edit_repo', repo_name=ID)
634 634 c.repo_info = self._load_repo(repo_name)
635 635 c.active = 'caches'
636 636
637 637 return render('admin/repos/repo_edit.html')
638 638
639 639 @HasRepoPermissionAllDecorator('repository.admin')
640 640 @auth.CSRFRequired()
641 641 def edit_remote(self, repo_name):
642 642 """PUT /{repo_name}/settings/remote: edit the repo remote."""
643 643 try:
644 644 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
645 645 h.flash(_('Pulled from remote location'), category='success')
646 646 except Exception:
647 647 log.exception("Exception during pull from remote")
648 648 h.flash(_('An error occurred during pull from remote location'),
649 649 category='error')
650 650 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
651 651
652 652 @HasRepoPermissionAllDecorator('repository.admin')
653 653 def edit_remote_form(self, repo_name):
654 654 """GET /repo_name/settings: Form to edit an existing item"""
655 655 # url('edit_repo', repo_name=ID)
656 656 c.repo_info = self._load_repo(repo_name)
657 657 c.active = 'remote'
658 658
659 659 return render('admin/repos/repo_edit.html')
660 660
661 661 @HasRepoPermissionAllDecorator('repository.admin')
662 662 @auth.CSRFRequired()
663 663 def edit_statistics(self, repo_name):
664 664 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
665 665 try:
666 666 RepoModel().delete_stats(repo_name)
667 667 Session().commit()
668 668 except Exception as e:
669 669 log.error(traceback.format_exc())
670 670 h.flash(_('An error occurred during deletion of repository stats'),
671 671 category='error')
672 672 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
673 673
674 674 @HasRepoPermissionAllDecorator('repository.admin')
675 675 def edit_statistics_form(self, repo_name):
676 676 """GET /repo_name/settings: Form to edit an existing item"""
677 677 # url('edit_repo', repo_name=ID)
678 678 c.repo_info = self._load_repo(repo_name)
679 679 repo = c.repo_info.scm_instance()
680 680
681 681 if c.repo_info.stats:
682 682 # this is on what revision we ended up so we add +1 for count
683 683 last_rev = c.repo_info.stats.stat_on_revision + 1
684 684 else:
685 685 last_rev = 0
686 686 c.stats_revision = last_rev
687 687
688 688 c.repo_last_rev = repo.count()
689 689
690 690 if last_rev == 0 or c.repo_last_rev == 0:
691 691 c.stats_percentage = 0
692 692 else:
693 693 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
694 694
695 695 c.active = 'statistics'
696 696
697 697 return render('admin/repos/repo_edit.html')
698 698
699 699 @HasRepoPermissionAllDecorator('repository.admin')
700 700 @auth.CSRFRequired()
701 701 def repo_issuetracker_test(self, repo_name):
702 702 if request.is_xhr:
703 703 return h.urlify_commit_message(
704 704 request.POST.get('test_text', ''),
705 705 repo_name)
706 706 else:
707 707 raise HTTPBadRequest()
708 708
709 709 @HasRepoPermissionAllDecorator('repository.admin')
710 710 @auth.CSRFRequired()
711 711 def repo_issuetracker_delete(self, repo_name):
712 712 uid = request.POST.get('uid')
713 713 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
714 714 try:
715 715 repo_settings.delete_entries(uid)
716 716 except Exception:
717 717 h.flash(_('Error occurred during deleting issue tracker entry'),
718 718 category='error')
719 719 else:
720 720 h.flash(_('Removed issue tracker entry'), category='success')
721 721 return redirect(url('repo_settings_issuetracker',
722 722 repo_name=repo_name))
723 723
724 724 def _update_patterns(self, form, repo_settings):
725 725 for uid in form['delete_patterns']:
726 726 repo_settings.delete_entries(uid)
727 727
728 728 for pattern in form['patterns']:
729 729 for setting, value, type_ in pattern:
730 730 sett = repo_settings.create_or_update_setting(
731 731 setting, value, type_)
732 732 Session().add(sett)
733 733
734 734 Session().commit()
735 735
736 736 @HasRepoPermissionAllDecorator('repository.admin')
737 737 @auth.CSRFRequired()
738 738 def repo_issuetracker_save(self, repo_name):
739 739 # Save inheritance
740 740 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
741 741 inherited = (request.POST.get('inherit_global_issuetracker')
742 742 == "inherited")
743 743 repo_settings.inherit_global_settings = inherited
744 744 Session().commit()
745 745
746 746 form = IssueTrackerPatternsForm()().to_python(request.POST)
747 747 if form:
748 748 self._update_patterns(form, repo_settings)
749 749
750 750 h.flash(_('Updated issue tracker entries'), category='success')
751 751 return redirect(url('repo_settings_issuetracker',
752 752 repo_name=repo_name))
753 753
754 754 @HasRepoPermissionAllDecorator('repository.admin')
755 755 def repo_issuetracker(self, repo_name):
756 756 """GET /admin/settings/issue-tracker: All items in the collection"""
757 757 c.active = 'issuetracker'
758 758 c.data = 'data'
759 759 c.repo_info = self._load_repo(repo_name)
760 760
761 761 repo = Repository.get_by_repo_name(repo_name)
762 762 c.settings_model = IssueTrackerSettingsModel(repo=repo)
763 763 c.global_patterns = c.settings_model.get_global_settings()
764 764 c.repo_patterns = c.settings_model.get_repo_settings()
765 765
766 766 return render('admin/repos/repo_edit.html')
767 767
768 768 @HasRepoPermissionAllDecorator('repository.admin')
769 769 def repo_settings_vcs(self, repo_name):
770 770 """GET /{repo_name}/settings/vcs/: All items in the collection"""
771 771
772 772 model = VcsSettingsModel(repo=repo_name)
773 773
774 774 c.active = 'vcs'
775 775 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
776 776 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
777 777 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
778 778 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
779 779 c.repo_info = self._load_repo(repo_name)
780 780 defaults = self._vcs_form_defaults(repo_name)
781 781 c.inherit_global_settings = defaults['inherit_global_settings']
782 782
783 783 return htmlfill.render(
784 784 render('admin/repos/repo_edit.html'),
785 785 defaults=defaults,
786 786 encoding="UTF-8",
787 787 force_defaults=False)
788 788
789 789 @HasRepoPermissionAllDecorator('repository.admin')
790 790 @auth.CSRFRequired()
791 791 def repo_settings_vcs_update(self, repo_name):
792 792 """POST /{repo_name}/settings/vcs/: All items in the collection"""
793 793 c.active = 'vcs'
794 794
795 795 model = VcsSettingsModel(repo=repo_name)
796 796 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
797 797 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
798 798 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
799 799 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
800 800 c.repo_info = self._load_repo(repo_name)
801 801 defaults = self._vcs_form_defaults(repo_name)
802 802 c.inherit_global_settings = defaults['inherit_global_settings']
803 803
804 804 application_form = RepoVcsSettingsForm(repo_name)()
805 805 try:
806 806 form_result = application_form.to_python(dict(request.POST))
807 807 except formencode.Invalid as errors:
808 808 h.flash(
809 809 _("Some form inputs contain invalid data."),
810 810 category='error')
811 811 return htmlfill.render(
812 812 render('admin/repos/repo_edit.html'),
813 813 defaults=errors.value,
814 814 errors=errors.error_dict or {},
815 815 prefix_error=False,
816 816 encoding="UTF-8",
817 817 force_defaults=False
818 818 )
819 819
820 820 try:
821 821 inherit_global_settings = form_result['inherit_global_settings']
822 822 model.create_or_update_repo_settings(
823 823 form_result, inherit_global_settings=inherit_global_settings)
824 824 except Exception:
825 825 log.exception("Exception while updating settings")
826 826 h.flash(
827 827 _('Error occurred during updating repository VCS settings'),
828 828 category='error')
829 829 else:
830 830 Session().commit()
831 831 h.flash(_('Updated VCS settings'), category='success')
832 832 return redirect(url('repo_vcs_settings', repo_name=repo_name))
833 833
834 834 return htmlfill.render(
835 835 render('admin/repos/repo_edit.html'),
836 836 defaults=self._vcs_form_defaults(repo_name),
837 837 encoding="UTF-8",
838 838 force_defaults=False)
839 839
840 840 @HasRepoPermissionAllDecorator('repository.admin')
841 841 @auth.CSRFRequired()
842 842 @jsonify
843 843 def repo_delete_svn_pattern(self, repo_name):
844 844 if not request.is_xhr:
845 845 return False
846 846
847 847 delete_pattern_id = request.POST.get('delete_svn_pattern')
848 848 model = VcsSettingsModel(repo=repo_name)
849 849 try:
850 850 model.delete_repo_svn_pattern(delete_pattern_id)
851 851 except SettingNotFound:
852 852 raise HTTPBadRequest()
853 853
854 854 Session().commit()
855 855 return True
856 856
857 857 def _vcs_form_defaults(self, repo_name):
858 858 model = VcsSettingsModel(repo=repo_name)
859 859 global_defaults = model.get_global_settings()
860 860
861 861 repo_defaults = {}
862 862 repo_defaults.update(global_defaults)
863 863 repo_defaults.update(model.get_repo_settings())
864 864
865 865 global_defaults = {
866 866 '{}_inherited'.format(k): global_defaults[k]
867 867 for k in global_defaults}
868 868
869 869 defaults = {
870 870 'inherit_global_settings': model.inherit_global_settings
871 871 }
872 872 defaults.update(global_defaults)
873 873 defaults.update(repo_defaults)
874 874 defaults.update({
875 875 'new_svn_branch': '',
876 876 'new_svn_tag': '',
877 877 })
878 878 return defaults
@@ -1,480 +1,480 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 User Groups crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.orm import joinedload
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 38 RepoGroupAssignmentError
39 39 from rhodecode.lib.utils import jsonify, action_logger
40 40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
41 41 from rhodecode.lib.auth import (
42 42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
43 43 HasPermissionAnyDecorator)
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.permission import PermissionModel
46 46 from rhodecode.model.scm import UserGroupList
47 47 from rhodecode.model.user_group import UserGroupModel
48 48 from rhodecode.model.db import (
49 49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
50 50 from rhodecode.model.forms import (
51 51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
52 52 UserPermissionsForm)
53 53 from rhodecode.model.meta import Session
54 54 from rhodecode.lib.utils import action_logger
55 55 from rhodecode.lib.ext_json import json
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class UserGroupsController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62
63 63 @LoginRequired()
64 64 def __before__(self):
65 65 super(UserGroupsController, self).__before__()
66 66 c.available_permissions = config['available_permissions']
67 67 PermissionModel().set_global_permission_choices(c, translator=_)
68 68
69 69 def __load_data(self, user_group_id):
70 70 c.group_members_obj = [x.user for x in c.user_group.members]
71 71 c.group_members_obj.sort(key=lambda u: u.username.lower())
72 72
73 73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 74
75 75 c.available_members = [(x.user_id, x.username)
76 76 for x in User.query().all()]
77 77 c.available_members.sort(key=lambda u: u[1].lower())
78 78
79 79 def __load_defaults(self, user_group_id):
80 80 """
81 81 Load defaults settings for edit, and update
82 82
83 83 :param user_group_id:
84 84 """
85 85 user_group = UserGroup.get_or_404(user_group_id)
86 86 data = user_group.get_dict()
87 87 # fill owner
88 88 if user_group.user:
89 89 data.update({'user': user_group.user.username})
90 90 else:
91 91 replacement_user = User.get_first_admin().username
92 92 data.update({'user': replacement_user})
93 93 return data
94 94
95 95 def _revoke_perms_on_yourself(self, form_result):
96 96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 97 form_result['perm_updates'])
98 98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
99 99 form_result['perm_additions'])
100 100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
101 101 form_result['perm_deletions'])
102 102 admin_perm = 'usergroup.admin'
103 103 if _updates and _updates[0][1] != admin_perm or \
104 104 _additions and _additions[0][1] != admin_perm or \
105 105 _deletions and _deletions[0][1] != admin_perm:
106 106 return True
107 107 return False
108 108
109 109 # permission check inside
110 110 @NotAnonymous()
111 111 def index(self):
112 112 """GET /users_groups: All items in the collection"""
113 113 # url('users_groups')
114 114
115 115 from rhodecode.lib.utils import PartialRenderer
116 116 _render = PartialRenderer('data_table/_dt_elements.html')
117 117
118 118 def user_group_name(user_group_id, user_group_name):
119 119 return _render("user_group_name", user_group_id, user_group_name)
120 120
121 121 def user_group_actions(user_group_id, user_group_name):
122 122 return _render("user_group_actions", user_group_id, user_group_name)
123 123
124 124 ## json generate
125 125 group_iter = UserGroupList(UserGroup.query().all(),
126 126 perm_set=['usergroup.admin'])
127 127
128 128 user_groups_data = []
129 129 for user_gr in group_iter:
130 130 user_groups_data.append({
131 131 "group_name": user_group_name(
132 132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
133 133 "group_name_raw": user_gr.users_group_name,
134 134 "desc": h.escape(user_gr.user_group_description),
135 135 "members": len(user_gr.members),
136 136 "active": h.bool2icon(user_gr.users_group_active),
137 137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
138 138 "action": user_group_actions(
139 139 user_gr.users_group_id, user_gr.users_group_name)
140 140 })
141 141
142 142 c.data = json.dumps(user_groups_data)
143 143 return render('admin/user_groups/user_groups.html')
144 144
145 145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
146 146 @auth.CSRFRequired()
147 147 def create(self):
148 148 """POST /users_groups: Create a new item"""
149 149 # url('users_groups')
150 150
151 151 users_group_form = UserGroupForm()()
152 152 try:
153 153 form_result = users_group_form.to_python(dict(request.POST))
154 154 user_group = UserGroupModel().create(
155 155 name=form_result['users_group_name'],
156 156 description=form_result['user_group_description'],
157 157 owner=c.rhodecode_user.user_id,
158 158 active=form_result['users_group_active'])
159 159 Session().flush()
160 160
161 161 user_group_name = form_result['users_group_name']
162 162 action_logger(c.rhodecode_user,
163 163 'admin_created_users_group:%s' % user_group_name,
164 164 None, self.ip_addr, self.sa)
165 165 user_group_link = h.link_to(h.escape(user_group_name),
166 166 url('edit_users_group',
167 167 user_group_id=user_group.users_group_id))
168 168 h.flash(h.literal(_('Created user group %(user_group_link)s')
169 169 % {'user_group_link': user_group_link}),
170 170 category='success')
171 171 Session().commit()
172 172 except formencode.Invalid as errors:
173 173 return htmlfill.render(
174 174 render('admin/user_groups/user_group_add.html'),
175 175 defaults=errors.value,
176 176 errors=errors.error_dict or {},
177 177 prefix_error=False,
178 178 encoding="UTF-8",
179 179 force_defaults=False)
180 180 except Exception:
181 181 log.exception("Exception creating user group")
182 182 h.flash(_('Error occurred during creation of user group %s') \
183 183 % request.POST.get('users_group_name'), category='error')
184 184
185 185 return redirect(
186 186 url('edit_users_group', user_group_id=user_group.users_group_id))
187 187
188 188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
189 189 def new(self):
190 190 """GET /user_groups/new: Form to create a new item"""
191 191 # url('new_users_group')
192 192 return render('admin/user_groups/user_group_add.html')
193 193
194 194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
195 195 @auth.CSRFRequired()
196 196 def update(self, user_group_id):
197 197 """PUT /user_groups/user_group_id: Update an existing item"""
198 198 # Forms posted to this method should contain a hidden field:
199 199 # <input type="hidden" name="_method" value="PUT" />
200 200 # Or using helpers:
201 201 # h.form(url('users_group', user_group_id=ID),
202 202 # method='put')
203 203 # url('users_group', user_group_id=ID)
204 204
205 205 user_group_id = safe_int(user_group_id)
206 206 c.user_group = UserGroup.get_or_404(user_group_id)
207 207 c.active = 'settings'
208 208 self.__load_data(user_group_id)
209 209
210 210 available_members = [safe_unicode(x[0]) for x in c.available_members]
211 211
212 users_group_form = UserGroupForm(edit=True,
213 old_data=c.user_group.get_dict(),
214 available_members=available_members)()
212 users_group_form = UserGroupForm(
213 edit=True, old_data=c.user_group.get_dict(),
214 available_members=available_members, allow_disabled=True)()
215 215
216 216 try:
217 217 form_result = users_group_form.to_python(request.POST)
218 218 UserGroupModel().update(c.user_group, form_result)
219 219 gr = form_result['users_group_name']
220 220 action_logger(c.rhodecode_user,
221 221 'admin_updated_users_group:%s' % gr,
222 222 None, self.ip_addr, self.sa)
223 223 h.flash(_('Updated user group %s') % gr, category='success')
224 224 Session().commit()
225 225 except formencode.Invalid as errors:
226 226 defaults = errors.value
227 227 e = errors.error_dict or {}
228 228
229 229 return htmlfill.render(
230 230 render('admin/user_groups/user_group_edit.html'),
231 231 defaults=defaults,
232 232 errors=e,
233 233 prefix_error=False,
234 234 encoding="UTF-8",
235 235 force_defaults=False)
236 236 except Exception:
237 237 log.exception("Exception during update of user group")
238 238 h.flash(_('Error occurred during update of user group %s')
239 239 % request.POST.get('users_group_name'), category='error')
240 240
241 241 return redirect(url('edit_users_group', user_group_id=user_group_id))
242 242
243 243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
244 244 @auth.CSRFRequired()
245 245 def delete(self, user_group_id):
246 246 """DELETE /user_groups/user_group_id: Delete an existing item"""
247 247 # Forms posted to this method should contain a hidden field:
248 248 # <input type="hidden" name="_method" value="DELETE" />
249 249 # Or using helpers:
250 250 # h.form(url('users_group', user_group_id=ID),
251 251 # method='delete')
252 252 # url('users_group', user_group_id=ID)
253 253 user_group_id = safe_int(user_group_id)
254 254 c.user_group = UserGroup.get_or_404(user_group_id)
255 255 force = str2bool(request.POST.get('force'))
256 256
257 257 try:
258 258 UserGroupModel().delete(c.user_group, force=force)
259 259 Session().commit()
260 260 h.flash(_('Successfully deleted user group'), category='success')
261 261 except UserGroupAssignedException as e:
262 262 h.flash(str(e), category='error')
263 263 except Exception:
264 264 log.exception("Exception during deletion of user group")
265 265 h.flash(_('An error occurred during deletion of user group'),
266 266 category='error')
267 267 return redirect(url('users_groups'))
268 268
269 269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
270 270 def edit(self, user_group_id):
271 271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
272 272 # url('edit_users_group', user_group_id=ID)
273 273
274 274 user_group_id = safe_int(user_group_id)
275 275 c.user_group = UserGroup.get_or_404(user_group_id)
276 276 c.active = 'settings'
277 277 self.__load_data(user_group_id)
278 278
279 279 defaults = self.__load_defaults(user_group_id)
280 280
281 281 return htmlfill.render(
282 282 render('admin/user_groups/user_group_edit.html'),
283 283 defaults=defaults,
284 284 encoding="UTF-8",
285 285 force_defaults=False
286 286 )
287 287
288 288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
289 289 def edit_perms(self, user_group_id):
290 290 user_group_id = safe_int(user_group_id)
291 291 c.user_group = UserGroup.get_or_404(user_group_id)
292 292 c.active = 'perms'
293 293
294 294 defaults = {}
295 295 # fill user group users
296 296 for p in c.user_group.user_user_group_to_perm:
297 297 defaults.update({'u_perm_%s' % p.user.user_id:
298 298 p.permission.permission_name})
299 299
300 300 for p in c.user_group.user_group_user_group_to_perm:
301 301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
302 302 p.permission.permission_name})
303 303
304 304 return htmlfill.render(
305 305 render('admin/user_groups/user_group_edit.html'),
306 306 defaults=defaults,
307 307 encoding="UTF-8",
308 308 force_defaults=False
309 309 )
310 310
311 311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
312 312 @auth.CSRFRequired()
313 313 def update_perms(self, user_group_id):
314 314 """
315 315 grant permission for given usergroup
316 316
317 317 :param user_group_id:
318 318 """
319 319 user_group_id = safe_int(user_group_id)
320 320 c.user_group = UserGroup.get_or_404(user_group_id)
321 321 form = UserGroupPermsForm()().to_python(request.POST)
322 322
323 323 if not c.rhodecode_user.is_admin:
324 324 if self._revoke_perms_on_yourself(form):
325 325 msg = _('Cannot change permission for yourself as admin')
326 326 h.flash(msg, category='warning')
327 327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328 328
329 329 try:
330 330 UserGroupModel().update_permissions(user_group_id,
331 331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
332 332 except RepoGroupAssignmentError:
333 333 h.flash(_('Target group cannot be the same'), category='error')
334 334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335 335 #TODO: implement this
336 336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
337 337 # repo_name, self.ip_addr, self.sa)
338 338 Session().commit()
339 339 h.flash(_('User Group permissions updated'), category='success')
340 340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
341 341
342 342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
343 343 def edit_perms_summary(self, user_group_id):
344 344 user_group_id = safe_int(user_group_id)
345 345 c.user_group = UserGroup.get_or_404(user_group_id)
346 346 c.active = 'perms_summary'
347 347 permissions = {
348 348 'repositories': {},
349 349 'repositories_groups': {},
350 350 }
351 351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
352 352 .options(joinedload(UserGroupRepoToPerm.permission))\
353 353 .options(joinedload(UserGroupRepoToPerm.repository))\
354 354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
355 355 .all()
356 356
357 357 for gr in ugroup_repo_perms:
358 358 permissions['repositories'][gr.repository.repo_name] \
359 359 = gr.permission.permission_name
360 360
361 361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
362 362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
363 363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
364 364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
365 365 .all()
366 366
367 367 for gr in ugroup_group_perms:
368 368 permissions['repositories_groups'][gr.group.group_name] \
369 369 = gr.permission.permission_name
370 370 c.permissions = permissions
371 371 return render('admin/user_groups/user_group_edit.html')
372 372
373 373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
374 374 def edit_global_perms(self, user_group_id):
375 375 user_group_id = safe_int(user_group_id)
376 376 c.user_group = UserGroup.get_or_404(user_group_id)
377 377 c.active = 'global_perms'
378 378
379 379 c.default_user = User.get_default_user()
380 380 defaults = c.user_group.get_dict()
381 381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
382 382 defaults.update(c.user_group.get_default_perms())
383 383
384 384 return htmlfill.render(
385 385 render('admin/user_groups/user_group_edit.html'),
386 386 defaults=defaults,
387 387 encoding="UTF-8",
388 388 force_defaults=False
389 389 )
390 390
391 391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
392 392 @auth.CSRFRequired()
393 393 def update_global_perms(self, user_group_id):
394 394 """PUT /users_perm/user_group_id: Update an existing item"""
395 395 # url('users_group_perm', user_group_id=ID, method='put')
396 396 user_group_id = safe_int(user_group_id)
397 397 user_group = UserGroup.get_or_404(user_group_id)
398 398 c.active = 'global_perms'
399 399
400 400 try:
401 401 # first stage that verifies the checkbox
402 402 _form = UserIndividualPermissionsForm()
403 403 form_result = _form.to_python(dict(request.POST))
404 404 inherit_perms = form_result['inherit_default_permissions']
405 405 user_group.inherit_default_permissions = inherit_perms
406 406 Session().add(user_group)
407 407
408 408 if not inherit_perms:
409 409 # only update the individual ones if we un check the flag
410 410 _form = UserPermissionsForm(
411 411 [x[0] for x in c.repo_create_choices],
412 412 [x[0] for x in c.repo_create_on_write_choices],
413 413 [x[0] for x in c.repo_group_create_choices],
414 414 [x[0] for x in c.user_group_create_choices],
415 415 [x[0] for x in c.fork_choices],
416 416 [x[0] for x in c.inherit_default_permission_choices])()
417 417
418 418 form_result = _form.to_python(dict(request.POST))
419 419 form_result.update({'perm_user_group_id': user_group.users_group_id})
420 420
421 421 PermissionModel().update_user_group_permissions(form_result)
422 422
423 423 Session().commit()
424 424 h.flash(_('User Group global permissions updated successfully'),
425 425 category='success')
426 426
427 427 except formencode.Invalid as errors:
428 428 defaults = errors.value
429 429 c.user_group = user_group
430 430 return htmlfill.render(
431 431 render('admin/user_groups/user_group_edit.html'),
432 432 defaults=defaults,
433 433 errors=errors.error_dict or {},
434 434 prefix_error=False,
435 435 encoding="UTF-8",
436 436 force_defaults=False)
437 437
438 438 except Exception:
439 439 log.exception("Exception during permissions saving")
440 440 h.flash(_('An error occurred during permissions saving'),
441 441 category='error')
442 442
443 443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
444 444
445 445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
446 446 def edit_advanced(self, user_group_id):
447 447 user_group_id = safe_int(user_group_id)
448 448 c.user_group = UserGroup.get_or_404(user_group_id)
449 449 c.active = 'advanced'
450 450 c.group_members_obj = sorted(
451 451 (x.user for x in c.user_group.members),
452 452 key=lambda u: u.username.lower())
453 453
454 454 c.group_to_repos = sorted(
455 455 (x.repository for x in c.user_group.users_group_repo_to_perm),
456 456 key=lambda u: u.repo_name.lower())
457 457
458 458 c.group_to_repo_groups = sorted(
459 459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
460 460 key=lambda u: u.group_name.lower())
461 461
462 462 return render('admin/user_groups/user_group_edit.html')
463 463
464 464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
465 465 def edit_members(self, user_group_id):
466 466 user_group_id = safe_int(user_group_id)
467 467 c.user_group = UserGroup.get_or_404(user_group_id)
468 468 c.active = 'members'
469 469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
470 470 key=lambda u: u.username.lower())
471 471
472 472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
473 473
474 474 if request.is_xhr:
475 475 return jsonify(lambda *a, **k: {
476 476 'members': group_members
477 477 })
478 478
479 479 c.group_members = group_members
480 480 return render('admin/user_groups/user_group_edit.html')
@@ -1,547 +1,561 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import logging
45 45
46 46 import formencode
47 47 from formencode import All, Pipe
48 48
49 49 from pylons.i18n.translation import _
50 50
51 51 from rhodecode import BACKENDS
52 52 from rhodecode.model import validators as v
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def LoginForm():
58 58 class _LoginForm(formencode.Schema):
59 59 allow_extra_fields = True
60 60 filter_extra_fields = True
61 61 username = v.UnicodeString(
62 62 strip=True,
63 63 min=1,
64 64 not_empty=True,
65 65 messages={
66 66 'empty': _(u'Please enter a login'),
67 67 'tooShort': _(u'Enter a value %(min)i characters long or more')
68 68 }
69 69 )
70 70
71 71 password = v.UnicodeString(
72 72 strip=False,
73 73 min=3,
74 74 not_empty=True,
75 75 messages={
76 76 'empty': _(u'Please enter a password'),
77 77 'tooShort': _(u'Enter %(min)i characters or more')}
78 78 )
79 79
80 80 remember = v.StringBoolean(if_missing=False)
81 81
82 82 chained_validators = [v.ValidAuth()]
83 83 return _LoginForm
84 84
85 85
86 86 def PasswordChangeForm(username):
87 87 class _PasswordChangeForm(formencode.Schema):
88 88 allow_extra_fields = True
89 89 filter_extra_fields = True
90 90
91 91 current_password = v.ValidOldPassword(username)(not_empty=True)
92 92 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
93 93 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
94 94
95 95 chained_validators = [v.ValidPasswordsMatch('new_password',
96 96 'new_password_confirmation')]
97 97 return _PasswordChangeForm
98 98
99 99
100 100 def UserForm(edit=False, available_languages=[], old_data={}):
101 101 class _UserForm(formencode.Schema):
102 102 allow_extra_fields = True
103 103 filter_extra_fields = True
104 104 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
105 105 v.ValidUsername(edit, old_data))
106 106 if edit:
107 107 new_password = All(
108 108 v.ValidPassword(),
109 109 v.UnicodeString(strip=False, min=6, not_empty=False)
110 110 )
111 111 password_confirmation = All(
112 112 v.ValidPassword(),
113 113 v.UnicodeString(strip=False, min=6, not_empty=False),
114 114 )
115 115 admin = v.StringBoolean(if_missing=False)
116 116 else:
117 117 password = All(
118 118 v.ValidPassword(),
119 119 v.UnicodeString(strip=False, min=6, not_empty=True)
120 120 )
121 121 password_confirmation = All(
122 122 v.ValidPassword(),
123 123 v.UnicodeString(strip=False, min=6, not_empty=False)
124 124 )
125 125
126 126 password_change = v.StringBoolean(if_missing=False)
127 127 create_repo_group = v.StringBoolean(if_missing=False)
128 128
129 129 active = v.StringBoolean(if_missing=False)
130 130 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
131 131 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
132 132 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
133 133 extern_name = v.UnicodeString(strip=True)
134 134 extern_type = v.UnicodeString(strip=True)
135 135 language = v.OneOf(available_languages, hideList=False,
136 136 testValueList=True, if_missing=None)
137 137 chained_validators = [v.ValidPasswordsMatch()]
138 138 return _UserForm
139 139
140 140
141 def UserGroupForm(edit=False, old_data={}, available_members=[]):
141 def UserGroupForm(edit=False, old_data=None, available_members=None,
142 allow_disabled=False):
143 old_data = old_data or {}
144 available_members = available_members or []
145
142 146 class _UserGroupForm(formencode.Schema):
143 147 allow_extra_fields = True
144 148 filter_extra_fields = True
145 149
146 150 users_group_name = All(
147 151 v.UnicodeString(strip=True, min=1, not_empty=True),
148 152 v.ValidUserGroup(edit, old_data)
149 153 )
150 154 user_group_description = v.UnicodeString(strip=True, min=1,
151 155 not_empty=False)
152 156
153 157 users_group_active = v.StringBoolean(if_missing=False)
154 158
155 159 if edit:
156 160 users_group_members = v.OneOf(
157 161 available_members, hideList=False, testValueList=True,
158 162 if_missing=None, not_empty=False
159 163 )
160 #this is user group owner
161 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
162
164 # this is user group owner
165 user = All(
166 v.UnicodeString(not_empty=True),
167 v.ValidRepoUser(allow_disabled))
163 168 return _UserGroupForm
164 169
165 170
166 def RepoGroupForm(edit=False, old_data={}, available_groups=[],
167 can_create_in_root=False):
171 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
172 can_create_in_root=False, allow_disabled=False):
173 old_data = old_data or {}
174 available_groups = available_groups or []
175
168 176 class _RepoGroupForm(formencode.Schema):
169 177 allow_extra_fields = True
170 178 filter_extra_fields = False
171 179
172 180 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
173 181 v.SlugifyName(),)
174 182 group_description = v.UnicodeString(strip=True, min=1,
175 183 not_empty=False)
176 184 group_copy_permissions = v.StringBoolean(if_missing=False)
177 185
178 186 group_parent_id = v.OneOf(available_groups, hideList=False,
179 187 testValueList=True, not_empty=True)
180 188 enable_locking = v.StringBoolean(if_missing=False)
181 chained_validators = [v.ValidRepoGroup(edit, old_data, can_create_in_root)]
189 chained_validators = [
190 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
182 191
183 192 if edit:
184 #this is repo group owner
185 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
193 # this is repo group owner
194 user = All(
195 v.UnicodeString(not_empty=True),
196 v.ValidRepoUser(allow_disabled))
186 197
187 198 return _RepoGroupForm
188 199
189 200
190 201 def RegisterForm(edit=False, old_data={}):
191 202 class _RegisterForm(formencode.Schema):
192 203 allow_extra_fields = True
193 204 filter_extra_fields = True
194 205 username = All(
195 206 v.ValidUsername(edit, old_data),
196 207 v.UnicodeString(strip=True, min=1, not_empty=True)
197 208 )
198 209 password = All(
199 210 v.ValidPassword(),
200 211 v.UnicodeString(strip=False, min=6, not_empty=True)
201 212 )
202 213 password_confirmation = All(
203 214 v.ValidPassword(),
204 215 v.UnicodeString(strip=False, min=6, not_empty=True)
205 216 )
206 217 active = v.StringBoolean(if_missing=False)
207 218 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
208 219 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
209 220 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
210 221
211 222 chained_validators = [v.ValidPasswordsMatch()]
212 223
213 224 return _RegisterForm
214 225
215 226
216 227 def PasswordResetForm():
217 228 class _PasswordResetForm(formencode.Schema):
218 229 allow_extra_fields = True
219 230 filter_extra_fields = True
220 231 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
221 232 return _PasswordResetForm
222 233
223 234
224 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None):
235 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
236 allow_disabled=False):
225 237 old_data = old_data or {}
226 238 repo_groups = repo_groups or []
227 239 landing_revs = landing_revs or []
228 240 supported_backends = BACKENDS.keys()
229 241
230 242 class _RepoForm(formencode.Schema):
231 243 allow_extra_fields = True
232 244 filter_extra_fields = False
233 245 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
234 246 v.SlugifyName())
235 247 repo_group = All(v.CanWriteGroup(old_data),
236 248 v.OneOf(repo_groups, hideList=True))
237 249 repo_type = v.OneOf(supported_backends, required=False,
238 250 if_missing=old_data.get('repo_type'))
239 251 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
240 252 repo_private = v.StringBoolean(if_missing=False)
241 253 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
242 254 repo_copy_permissions = v.StringBoolean(if_missing=False)
243 255 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
244 256
245 257 repo_enable_statistics = v.StringBoolean(if_missing=False)
246 258 repo_enable_downloads = v.StringBoolean(if_missing=False)
247 259 repo_enable_locking = v.StringBoolean(if_missing=False)
248 260
249 261 if edit:
250 262 # this is repo owner
251 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
263 user = All(
264 v.UnicodeString(not_empty=True),
265 v.ValidRepoUser(allow_disabled))
252 266 clone_uri_change = v.UnicodeString(
253 267 not_empty=False, if_missing=v.Missing)
254 268
255 269 chained_validators = [v.ValidCloneUri(),
256 270 v.ValidRepoName(edit, old_data)]
257 271 return _RepoForm
258 272
259 273
260 274 def RepoPermsForm():
261 275 class _RepoPermsForm(formencode.Schema):
262 276 allow_extra_fields = True
263 277 filter_extra_fields = False
264 278 chained_validators = [v.ValidPerms(type_='repo')]
265 279 return _RepoPermsForm
266 280
267 281
268 282 def RepoGroupPermsForm(valid_recursive_choices):
269 283 class _RepoGroupPermsForm(formencode.Schema):
270 284 allow_extra_fields = True
271 285 filter_extra_fields = False
272 286 recursive = v.OneOf(valid_recursive_choices)
273 287 chained_validators = [v.ValidPerms(type_='repo_group')]
274 288 return _RepoGroupPermsForm
275 289
276 290
277 291 def UserGroupPermsForm():
278 292 class _UserPermsForm(formencode.Schema):
279 293 allow_extra_fields = True
280 294 filter_extra_fields = False
281 295 chained_validators = [v.ValidPerms(type_='user_group')]
282 296 return _UserPermsForm
283 297
284 298
285 299 def RepoFieldForm():
286 300 class _RepoFieldForm(formencode.Schema):
287 301 filter_extra_fields = True
288 302 allow_extra_fields = True
289 303
290 304 new_field_key = All(v.FieldKey(),
291 305 v.UnicodeString(strip=True, min=3, not_empty=True))
292 306 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
293 307 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
294 308 if_missing='str')
295 309 new_field_label = v.UnicodeString(not_empty=False)
296 310 new_field_desc = v.UnicodeString(not_empty=False)
297 311
298 312 return _RepoFieldForm
299 313
300 314
301 315 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
302 316 repo_groups=[], landing_revs=[]):
303 317 class _RepoForkForm(formencode.Schema):
304 318 allow_extra_fields = True
305 319 filter_extra_fields = False
306 320 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
307 321 v.SlugifyName())
308 322 repo_group = All(v.CanWriteGroup(),
309 323 v.OneOf(repo_groups, hideList=True))
310 324 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
311 325 description = v.UnicodeString(strip=True, min=1, not_empty=True)
312 326 private = v.StringBoolean(if_missing=False)
313 327 copy_permissions = v.StringBoolean(if_missing=False)
314 328 fork_parent_id = v.UnicodeString()
315 329 chained_validators = [v.ValidForkName(edit, old_data)]
316 330 landing_rev = v.OneOf(landing_revs, hideList=True)
317 331
318 332 return _RepoForkForm
319 333
320 334
321 335 def ApplicationSettingsForm():
322 336 class _ApplicationSettingsForm(formencode.Schema):
323 337 allow_extra_fields = True
324 338 filter_extra_fields = False
325 339 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
326 340 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
327 341 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
328 342 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
329 343 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
330 344 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
331 345
332 346 return _ApplicationSettingsForm
333 347
334 348
335 349 def ApplicationVisualisationForm():
336 350 class _ApplicationVisualisationForm(formencode.Schema):
337 351 allow_extra_fields = True
338 352 filter_extra_fields = False
339 353 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
340 354 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
341 355 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
342 356
343 357 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
344 358 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
345 359 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
346 360 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
347 361 rhodecode_show_version = v.StringBoolean(if_missing=False)
348 362 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
349 363 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
350 364 rhodecode_gravatar_url = v.UnicodeString(min=3)
351 365 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
352 366 rhodecode_support_url = v.UnicodeString()
353 367 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
354 368 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
355 369
356 370 return _ApplicationVisualisationForm
357 371
358 372
359 373 class _BaseVcsSettingsForm(formencode.Schema):
360 374 allow_extra_fields = True
361 375 filter_extra_fields = False
362 376 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
363 377 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
364 378 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
365 379
366 380 extensions_largefiles = v.StringBoolean(if_missing=False)
367 381 phases_publish = v.StringBoolean(if_missing=False)
368 382
369 383 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
370 384 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
371 385
372 386
373 387 def ApplicationUiSettingsForm():
374 388 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
375 389 web_push_ssl = v.StringBoolean(if_missing=False)
376 390 paths_root_path = All(
377 391 v.ValidPath(),
378 392 v.UnicodeString(strip=True, min=1, not_empty=True)
379 393 )
380 394 extensions_hgsubversion = v.StringBoolean(if_missing=False)
381 395 extensions_hggit = v.StringBoolean(if_missing=False)
382 396 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
383 397 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
384 398
385 399 return _ApplicationUiSettingsForm
386 400
387 401
388 402 def RepoVcsSettingsForm(repo_name):
389 403 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
390 404 inherit_global_settings = v.StringBoolean(if_missing=False)
391 405 new_svn_branch = v.ValidSvnPattern(
392 406 section='vcs_svn_branch', repo_name=repo_name)
393 407 new_svn_tag = v.ValidSvnPattern(
394 408 section='vcs_svn_tag', repo_name=repo_name)
395 409
396 410 return _RepoVcsSettingsForm
397 411
398 412
399 413 def LabsSettingsForm():
400 414 class _LabSettingsForm(formencode.Schema):
401 415 allow_extra_fields = True
402 416 filter_extra_fields = False
403 417
404 418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
405 419 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
406 420 if_missing=False)
407 421 rhodecode_subversion_http_server_url = v.UnicodeString(
408 422 strip=True, if_missing=None)
409 423
410 424 return _LabSettingsForm
411 425
412 426
413 427 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
414 428 class _DefaultPermissionsForm(formencode.Schema):
415 429 allow_extra_fields = True
416 430 filter_extra_fields = True
417 431
418 432 anonymous = v.StringBoolean(if_missing=False)
419 433 default_register = v.OneOf(register_choices)
420 434 default_register_message = v.UnicodeString()
421 435 default_extern_activate = v.OneOf(extern_activate_choices)
422 436
423 437 return _DefaultPermissionsForm
424 438
425 439
426 440 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
427 441 user_group_perms_choices):
428 442 class _ObjectPermissionsForm(formencode.Schema):
429 443 allow_extra_fields = True
430 444 filter_extra_fields = True
431 445 overwrite_default_repo = v.StringBoolean(if_missing=False)
432 446 overwrite_default_group = v.StringBoolean(if_missing=False)
433 447 overwrite_default_user_group = v.StringBoolean(if_missing=False)
434 448 default_repo_perm = v.OneOf(repo_perms_choices)
435 449 default_group_perm = v.OneOf(group_perms_choices)
436 450 default_user_group_perm = v.OneOf(user_group_perms_choices)
437 451
438 452 return _ObjectPermissionsForm
439 453
440 454
441 455 def UserPermissionsForm(create_choices, create_on_write_choices,
442 456 repo_group_create_choices, user_group_create_choices,
443 457 fork_choices, inherit_default_permissions_choices):
444 458 class _DefaultPermissionsForm(formencode.Schema):
445 459 allow_extra_fields = True
446 460 filter_extra_fields = True
447 461
448 462 anonymous = v.StringBoolean(if_missing=False)
449 463
450 464 default_repo_create = v.OneOf(create_choices)
451 465 default_repo_create_on_write = v.OneOf(create_on_write_choices)
452 466 default_user_group_create = v.OneOf(user_group_create_choices)
453 467 default_repo_group_create = v.OneOf(repo_group_create_choices)
454 468 default_fork_create = v.OneOf(fork_choices)
455 469 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
456 470
457 471 return _DefaultPermissionsForm
458 472
459 473
460 474 def UserIndividualPermissionsForm():
461 475 class _DefaultPermissionsForm(formencode.Schema):
462 476 allow_extra_fields = True
463 477 filter_extra_fields = True
464 478
465 479 inherit_default_permissions = v.StringBoolean(if_missing=False)
466 480
467 481 return _DefaultPermissionsForm
468 482
469 483
470 484 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
471 485 class _DefaultsForm(formencode.Schema):
472 486 allow_extra_fields = True
473 487 filter_extra_fields = True
474 488 default_repo_type = v.OneOf(supported_backends)
475 489 default_repo_private = v.StringBoolean(if_missing=False)
476 490 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
477 491 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
478 492 default_repo_enable_locking = v.StringBoolean(if_missing=False)
479 493
480 494 return _DefaultsForm
481 495
482 496
483 497 def AuthSettingsForm():
484 498 class _AuthSettingsForm(formencode.Schema):
485 499 allow_extra_fields = True
486 500 filter_extra_fields = True
487 501 auth_plugins = All(v.ValidAuthPlugins(),
488 502 v.UniqueListFromString()(not_empty=True))
489 503
490 504 return _AuthSettingsForm
491 505
492 506
493 507 def UserExtraEmailForm():
494 508 class _UserExtraEmailForm(formencode.Schema):
495 509 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
496 510 return _UserExtraEmailForm
497 511
498 512
499 513 def UserExtraIpForm():
500 514 class _UserExtraIpForm(formencode.Schema):
501 515 ip = v.ValidIp()(not_empty=True)
502 516 return _UserExtraIpForm
503 517
504 518
505 519 def PullRequestForm(repo_id):
506 520 class _PullRequestForm(formencode.Schema):
507 521 allow_extra_fields = True
508 522 filter_extra_fields = True
509 523
510 524 user = v.UnicodeString(strip=True, required=True)
511 525 source_repo = v.UnicodeString(strip=True, required=True)
512 526 source_ref = v.UnicodeString(strip=True, required=True)
513 527 target_repo = v.UnicodeString(strip=True, required=True)
514 528 target_ref = v.UnicodeString(strip=True, required=True)
515 529 revisions = All(#v.NotReviewedRevisions(repo_id)(),
516 530 v.UniqueList()(not_empty=True))
517 531 review_members = v.UniqueList(convert=int)(not_empty=True)
518 532
519 533 pullrequest_title = v.UnicodeString(strip=True, required=True)
520 534 pullrequest_desc = v.UnicodeString(strip=True, required=False)
521 535
522 536 return _PullRequestForm
523 537
524 538
525 539 def GistForm(lifetime_options, acl_level_options):
526 540 class _GistForm(formencode.Schema):
527 541
528 542 gistid = All(v.UniqGistId(), v.UnicodeString(strip=True, min=3, not_empty=False, if_missing=None))
529 543 filename = All(v.BasePath()(),
530 544 v.UnicodeString(strip=True, required=False))
531 545 description = v.UnicodeString(required=False, if_missing=u'')
532 546 lifetime = v.OneOf(lifetime_options)
533 547 mimetype = v.UnicodeString(required=False, if_missing=None)
534 548 content = v.UnicodeString(required=True, not_empty=True)
535 549 public = v.UnicodeString(required=False, if_missing=u'')
536 550 private = v.UnicodeString(required=False, if_missing=u'')
537 551 acl_level = v.OneOf(acl_level_options)
538 552
539 553 return _GistForm
540 554
541 555
542 556 def IssueTrackerPatternsForm():
543 557 class _IssueTrackerPatternsForm(formencode.Schema):
544 558 allow_extra_fields = True
545 559 filter_extra_fields = False
546 560 chained_validators = [v.ValidPattern()]
547 561 return _IssueTrackerPatternsForm
@@ -1,1120 +1,1125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 41 from rhodecode.authentication import (
42 42 legacy_plugin_prefix, _import_legacy_plugin)
43 43 from rhodecode.authentication.base import loadplugin
44 44 from rhodecode.config.routing import ADMIN_PREFIX
45 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
48 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 51 from rhodecode.model.db import (
52 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 # silence warnings and pylint
56 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class _Missing(object):
63 63 pass
64 64
65 65 Missing = _Missing()
66 66
67 67
68 68 class StateObj(object):
69 69 """
70 70 this is needed to translate the messages using _() in validators
71 71 """
72 72 _ = staticmethod(_)
73 73
74 74
75 75 def M(self, key, state=None, **kwargs):
76 76 """
77 77 returns string from self.message based on given key,
78 78 passed kw params are used to substitute %(named)s params inside
79 79 translated strings
80 80
81 81 :param msg:
82 82 :param state:
83 83 """
84 84 if state is None:
85 85 state = StateObj()
86 86 else:
87 87 state._ = staticmethod(_)
88 88 # inject validator into state object
89 89 return self.message(key, state, **kwargs)
90 90
91 91
92 92 def UniqueList(convert=None):
93 93 class _UniqueList(formencode.FancyValidator):
94 94 """
95 95 Unique List !
96 96 """
97 97 messages = {
98 98 'empty': _(u'Value cannot be an empty list'),
99 99 'missing_value': _(u'Value cannot be an empty list'),
100 100 }
101 101
102 102 def _to_python(self, value, state):
103 103 ret_val = []
104 104
105 105 def make_unique(value):
106 106 seen = []
107 107 return [c for c in value if not (c in seen or seen.append(c))]
108 108
109 109 if isinstance(value, list):
110 110 ret_val = make_unique(value)
111 111 elif isinstance(value, set):
112 112 ret_val = make_unique(list(value))
113 113 elif isinstance(value, tuple):
114 114 ret_val = make_unique(list(value))
115 115 elif value is None:
116 116 ret_val = []
117 117 else:
118 118 ret_val = [value]
119 119
120 120 if convert:
121 121 ret_val = map(convert, ret_val)
122 122 return ret_val
123 123
124 124 def empty_value(self, value):
125 125 return []
126 126
127 127 return _UniqueList
128 128
129 129
130 130 def UniqueListFromString():
131 131 class _UniqueListFromString(UniqueList()):
132 132 def _to_python(self, value, state):
133 133 if isinstance(value, basestring):
134 134 value = aslist(value, ',')
135 135 return super(_UniqueListFromString, self)._to_python(value, state)
136 136 return _UniqueListFromString
137 137
138 138
139 139 def ValidSvnPattern(section, repo_name=None):
140 140 class _validator(formencode.validators.FancyValidator):
141 141 messages = {
142 142 'pattern_exists': _(u'Pattern already exists'),
143 143 }
144 144
145 145 def validate_python(self, value, state):
146 146 if not value:
147 147 return
148 148 model = VcsSettingsModel(repo=repo_name)
149 149 ui_settings = model.get_svn_patterns(section=section)
150 150 for entry in ui_settings:
151 151 if value == entry.value:
152 152 msg = M(self, 'pattern_exists', state)
153 153 raise formencode.Invalid(msg, value, state)
154 154 return _validator
155 155
156 156
157 157 def ValidUsername(edit=False, old_data={}):
158 158 class _validator(formencode.validators.FancyValidator):
159 159 messages = {
160 160 'username_exists': _(u'Username "%(username)s" already exists'),
161 161 'system_invalid_username':
162 162 _(u'Username "%(username)s" is forbidden'),
163 163 'invalid_username':
164 164 _(u'Username may only contain alphanumeric characters '
165 165 u'underscores, periods or dashes and must begin with '
166 166 u'alphanumeric character or underscore')
167 167 }
168 168
169 169 def validate_python(self, value, state):
170 170 if value in ['default', 'new_user']:
171 171 msg = M(self, 'system_invalid_username', state, username=value)
172 172 raise formencode.Invalid(msg, value, state)
173 173 # check if user is unique
174 174 old_un = None
175 175 if edit:
176 176 old_un = User.get(old_data.get('user_id')).username
177 177
178 178 if old_un != value or not edit:
179 179 if User.get_by_username(value, case_insensitive=True):
180 180 msg = M(self, 'username_exists', state, username=value)
181 181 raise formencode.Invalid(msg, value, state)
182 182
183 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 184 is None):
185 185 msg = M(self, 'invalid_username', state)
186 186 raise formencode.Invalid(msg, value, state)
187 187 return _validator
188 188
189 189
190 190 def ValidRegex(msg=None):
191 191 class _validator(formencode.validators.Regex):
192 192 messages = {'invalid': msg or _(u'The input is not valid')}
193 193 return _validator
194 194
195 195
196 def ValidRepoUser():
196 def ValidRepoUser(allow_disabled=False):
197 197 class _validator(formencode.validators.FancyValidator):
198 198 messages = {
199 'invalid_username': _(u'Username %(username)s is not valid')
199 'invalid_username': _(u'Username %(username)s is not valid'),
200 'disabled_username': _(u'Username %(username)s is disabled')
200 201 }
201 202
202 203 def validate_python(self, value, state):
203 204 try:
204 User.query().filter(User.active == true())\
205 .filter(User.username == value).one()
205 user = User.query().filter(User.username == value).one()
206 206 except Exception:
207 207 msg = M(self, 'invalid_username', state, username=value)
208 208 raise formencode.Invalid(
209 209 msg, value, state, error_dict={'username': msg}
210 210 )
211 if user and (not allow_disabled and not user.active):
212 msg = M(self, 'disabled_username', state, username=value)
213 raise formencode.Invalid(
214 msg, value, state, error_dict={'username': msg}
215 )
211 216
212 217 return _validator
213 218
214 219
215 220 def ValidUserGroup(edit=False, old_data={}):
216 221 class _validator(formencode.validators.FancyValidator):
217 222 messages = {
218 223 'invalid_group': _(u'Invalid user group name'),
219 224 'group_exist': _(u'User group "%(usergroup)s" already exists'),
220 225 'invalid_usergroup_name':
221 226 _(u'user group name may only contain alphanumeric '
222 227 u'characters underscores, periods or dashes and must begin '
223 228 u'with alphanumeric character')
224 229 }
225 230
226 231 def validate_python(self, value, state):
227 232 if value in ['default']:
228 233 msg = M(self, 'invalid_group', state)
229 234 raise formencode.Invalid(
230 235 msg, value, state, error_dict={'users_group_name': msg}
231 236 )
232 237 # check if group is unique
233 238 old_ugname = None
234 239 if edit:
235 240 old_id = old_data.get('users_group_id')
236 241 old_ugname = UserGroup.get(old_id).users_group_name
237 242
238 243 if old_ugname != value or not edit:
239 244 is_existing_group = UserGroup.get_by_group_name(
240 245 value, case_insensitive=True)
241 246 if is_existing_group:
242 247 msg = M(self, 'group_exist', state, usergroup=value)
243 248 raise formencode.Invalid(
244 249 msg, value, state, error_dict={'users_group_name': msg}
245 250 )
246 251
247 252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
248 253 msg = M(self, 'invalid_usergroup_name', state)
249 254 raise formencode.Invalid(
250 255 msg, value, state, error_dict={'users_group_name': msg}
251 256 )
252 257
253 258 return _validator
254 259
255 260
256 261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
257 262 class _validator(formencode.validators.FancyValidator):
258 263 messages = {
259 264 'group_parent_id': _(u'Cannot assign this group as parent'),
260 265 'group_exists': _(u'Group "%(group_name)s" already exists'),
261 266 'repo_exists': _(u'Repository with name "%(group_name)s" '
262 267 u'already exists'),
263 268 'permission_denied': _(u"no permission to store repository group"
264 269 u"in this location"),
265 270 'permission_denied_root': _(
266 271 u"no permission to store repository group "
267 272 u"in root location")
268 273 }
269 274
270 275 def _to_python(self, value, state):
271 276 group_name = repo_name_slug(value.get('group_name', ''))
272 277 group_parent_id = safe_int(value.get('group_parent_id'))
273 278 gr = RepoGroup.get(group_parent_id)
274 279 if gr:
275 280 parent_group_path = gr.full_path
276 281 # value needs to be aware of group name in order to check
277 282 # db key This is an actual just the name to store in the
278 283 # database
279 284 group_name_full = (
280 285 parent_group_path + RepoGroup.url_sep() + group_name)
281 286 else:
282 287 group_name_full = group_name
283 288
284 289 value['group_name'] = group_name
285 290 value['group_name_full'] = group_name_full
286 291 value['group_parent_id'] = group_parent_id
287 292 return value
288 293
289 294 def validate_python(self, value, state):
290 295
291 296 old_group_name = None
292 297 group_name = value.get('group_name')
293 298 group_name_full = value.get('group_name_full')
294 299 group_parent_id = safe_int(value.get('group_parent_id'))
295 300 if group_parent_id == -1:
296 301 group_parent_id = None
297 302
298 303 group_obj = RepoGroup.get(old_data.get('group_id'))
299 304 parent_group_changed = False
300 305 if edit:
301 306 old_group_name = group_obj.group_name
302 307 old_group_parent_id = group_obj.group_parent_id
303 308
304 309 if group_parent_id != old_group_parent_id:
305 310 parent_group_changed = True
306 311
307 312 # TODO: mikhail: the following if statement is not reached
308 313 # since group_parent_id's OneOf validation fails before.
309 314 # Can be removed.
310 315
311 316 # check against setting a parent of self
312 317 parent_of_self = (
313 318 old_data['group_id'] == group_parent_id
314 319 if group_parent_id else False
315 320 )
316 321 if parent_of_self:
317 322 msg = M(self, 'group_parent_id', state)
318 323 raise formencode.Invalid(
319 324 msg, value, state, error_dict={'group_parent_id': msg}
320 325 )
321 326
322 327 # group we're moving current group inside
323 328 child_group = None
324 329 if group_parent_id:
325 330 child_group = RepoGroup.query().filter(
326 331 RepoGroup.group_id == group_parent_id).scalar()
327 332
328 333 # do a special check that we cannot move a group to one of
329 334 # it's children
330 335 if edit and child_group:
331 336 parents = [x.group_id for x in child_group.parents]
332 337 move_to_children = old_data['group_id'] in parents
333 338 if move_to_children:
334 339 msg = M(self, 'group_parent_id', state)
335 340 raise formencode.Invalid(
336 341 msg, value, state, error_dict={'group_parent_id': msg})
337 342
338 343 # Check if we have permission to store in the parent.
339 344 # Only check if the parent group changed.
340 345 if parent_group_changed:
341 346 if child_group is None:
342 347 if not can_create_in_root:
343 348 msg = M(self, 'permission_denied_root', state)
344 349 raise formencode.Invalid(
345 350 msg, value, state,
346 351 error_dict={'group_parent_id': msg})
347 352 else:
348 353 valid = HasRepoGroupPermissionAny('group.admin')
349 354 forbidden = not valid(
350 355 child_group.group_name, 'can create group validator')
351 356 if forbidden:
352 357 msg = M(self, 'permission_denied', state)
353 358 raise formencode.Invalid(
354 359 msg, value, state,
355 360 error_dict={'group_parent_id': msg})
356 361
357 362 # if we change the name or it's new group, check for existing names
358 363 # or repositories with the same name
359 364 if old_group_name != group_name_full or not edit:
360 365 # check group
361 366 gr = RepoGroup.get_by_group_name(group_name_full)
362 367 if gr:
363 368 msg = M(self, 'group_exists', state, group_name=group_name)
364 369 raise formencode.Invalid(
365 370 msg, value, state, error_dict={'group_name': msg})
366 371
367 372 # check for same repo
368 373 repo = Repository.get_by_repo_name(group_name_full)
369 374 if repo:
370 375 msg = M(self, 'repo_exists', state, group_name=group_name)
371 376 raise formencode.Invalid(
372 377 msg, value, state, error_dict={'group_name': msg})
373 378
374 379 return _validator
375 380
376 381
377 382 def ValidPassword():
378 383 class _validator(formencode.validators.FancyValidator):
379 384 messages = {
380 385 'invalid_password':
381 386 _(u'Invalid characters (non-ascii) in password')
382 387 }
383 388
384 389 def validate_python(self, value, state):
385 390 try:
386 391 (value or '').decode('ascii')
387 392 except UnicodeError:
388 393 msg = M(self, 'invalid_password', state)
389 394 raise formencode.Invalid(msg, value, state,)
390 395 return _validator
391 396
392 397
393 398 def ValidOldPassword(username):
394 399 class _validator(formencode.validators.FancyValidator):
395 400 messages = {
396 401 'invalid_password': _(u'Invalid old password')
397 402 }
398 403
399 404 def validate_python(self, value, state):
400 405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
401 406 if not authenticate(username, value, '', HTTP_TYPE):
402 407 msg = M(self, 'invalid_password', state)
403 408 raise formencode.Invalid(
404 409 msg, value, state, error_dict={'current_password': msg}
405 410 )
406 411 return _validator
407 412
408 413
409 414 def ValidPasswordsMatch(
410 415 passwd='new_password', passwd_confirmation='password_confirmation'):
411 416 class _validator(formencode.validators.FancyValidator):
412 417 messages = {
413 418 'password_mismatch': _(u'Passwords do not match'),
414 419 }
415 420
416 421 def validate_python(self, value, state):
417 422
418 423 pass_val = value.get('password') or value.get(passwd)
419 424 if pass_val != value[passwd_confirmation]:
420 425 msg = M(self, 'password_mismatch', state)
421 426 raise formencode.Invalid(
422 427 msg, value, state,
423 428 error_dict={passwd: msg, passwd_confirmation: msg}
424 429 )
425 430 return _validator
426 431
427 432
428 433 def ValidAuth():
429 434 class _validator(formencode.validators.FancyValidator):
430 435 messages = {
431 436 'invalid_password': _(u'invalid password'),
432 437 'invalid_username': _(u'invalid user name'),
433 438 'disabled_account': _(u'Your account is disabled')
434 439 }
435 440
436 441 def validate_python(self, value, state):
437 442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
438 443
439 444 password = value['password']
440 445 username = value['username']
441 446
442 447 if not authenticate(username, password, '', HTTP_TYPE,
443 448 skip_missing=True):
444 449 user = User.get_by_username(username)
445 450 if user and not user.active:
446 451 log.warning('user %s is disabled', username)
447 452 msg = M(self, 'disabled_account', state)
448 453 raise formencode.Invalid(
449 454 msg, value, state, error_dict={'username': msg}
450 455 )
451 456 else:
452 457 log.warning('user `%s` failed to authenticate', username)
453 458 msg = M(self, 'invalid_username', state)
454 459 msg2 = M(self, 'invalid_password', state)
455 460 raise formencode.Invalid(
456 461 msg, value, state,
457 462 error_dict={'username': msg, 'password': msg2}
458 463 )
459 464 return _validator
460 465
461 466
462 467 def ValidAuthToken():
463 468 class _validator(formencode.validators.FancyValidator):
464 469 messages = {
465 470 'invalid_token': _(u'Token mismatch')
466 471 }
467 472
468 473 def validate_python(self, value, state):
469 474 if value != authentication_token():
470 475 msg = M(self, 'invalid_token', state)
471 476 raise formencode.Invalid(msg, value, state)
472 477 return _validator
473 478
474 479
475 480 def ValidRepoName(edit=False, old_data={}):
476 481 class _validator(formencode.validators.FancyValidator):
477 482 messages = {
478 483 'invalid_repo_name':
479 484 _(u'Repository name %(repo)s is disallowed'),
480 485 # top level
481 486 'repository_exists': _(u'Repository with name %(repo)s '
482 487 u'already exists'),
483 488 'group_exists': _(u'Repository group with name "%(repo)s" '
484 489 u'already exists'),
485 490 # inside a group
486 491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
487 492 u'exists in group "%(group)s"'),
488 493 'group_in_group_exists': _(
489 494 u'Repository group with name "%(repo)s" '
490 495 u'exists in group "%(group)s"'),
491 496 }
492 497
493 498 def _to_python(self, value, state):
494 499 repo_name = repo_name_slug(value.get('repo_name', ''))
495 500 repo_group = value.get('repo_group')
496 501 if repo_group:
497 502 gr = RepoGroup.get(repo_group)
498 503 group_path = gr.full_path
499 504 group_name = gr.group_name
500 505 # value needs to be aware of group name in order to check
501 506 # db key This is an actual just the name to store in the
502 507 # database
503 508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
504 509 else:
505 510 group_name = group_path = ''
506 511 repo_name_full = repo_name
507 512
508 513 value['repo_name'] = repo_name
509 514 value['repo_name_full'] = repo_name_full
510 515 value['group_path'] = group_path
511 516 value['group_name'] = group_name
512 517 return value
513 518
514 519 def validate_python(self, value, state):
515 520
516 521 repo_name = value.get('repo_name')
517 522 repo_name_full = value.get('repo_name_full')
518 523 group_path = value.get('group_path')
519 524 group_name = value.get('group_name')
520 525
521 526 if repo_name in [ADMIN_PREFIX, '']:
522 527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
523 528 raise formencode.Invalid(
524 529 msg, value, state, error_dict={'repo_name': msg})
525 530
526 531 rename = old_data.get('repo_name') != repo_name_full
527 532 create = not edit
528 533 if rename or create:
529 534
530 535 if group_path:
531 536 if Repository.get_by_repo_name(repo_name_full):
532 537 msg = M(self, 'repository_in_group_exists', state,
533 538 repo=repo_name, group=group_name)
534 539 raise formencode.Invalid(
535 540 msg, value, state, error_dict={'repo_name': msg})
536 541 if RepoGroup.get_by_group_name(repo_name_full):
537 542 msg = M(self, 'group_in_group_exists', state,
538 543 repo=repo_name, group=group_name)
539 544 raise formencode.Invalid(
540 545 msg, value, state, error_dict={'repo_name': msg})
541 546 else:
542 547 if RepoGroup.get_by_group_name(repo_name_full):
543 548 msg = M(self, 'group_exists', state, repo=repo_name)
544 549 raise formencode.Invalid(
545 550 msg, value, state, error_dict={'repo_name': msg})
546 551
547 552 if Repository.get_by_repo_name(repo_name_full):
548 553 msg = M(
549 554 self, 'repository_exists', state, repo=repo_name)
550 555 raise formencode.Invalid(
551 556 msg, value, state, error_dict={'repo_name': msg})
552 557 return value
553 558 return _validator
554 559
555 560
556 561 def ValidForkName(*args, **kwargs):
557 562 return ValidRepoName(*args, **kwargs)
558 563
559 564
560 565 def SlugifyName():
561 566 class _validator(formencode.validators.FancyValidator):
562 567
563 568 def _to_python(self, value, state):
564 569 return repo_name_slug(value)
565 570
566 571 def validate_python(self, value, state):
567 572 pass
568 573
569 574 return _validator
570 575
571 576
572 577 def ValidCloneUri():
573 578 class InvalidCloneUrl(Exception):
574 579 allowed_prefixes = ()
575 580
576 581 def url_handler(repo_type, url):
577 582 config = make_db_config(clear_session=False)
578 583 if repo_type == 'hg':
579 584 allowed_prefixes = ('http', 'svn+http', 'git+http')
580 585
581 586 if 'http' in url[:4]:
582 587 # initially check if it's at least the proper URL
583 588 # or does it pass basic auth
584 589 MercurialRepository.check_url(url, config)
585 590 elif 'svn+http' in url[:8]: # svn->hg import
586 591 SubversionRepository.check_url(url, config)
587 592 elif 'git+http' in url[:8]: # git->hg import
588 593 raise NotImplementedError()
589 594 else:
590 595 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
591 596 'Allowed url must start with one of %s'
592 597 % (url, ','.join(allowed_prefixes)))
593 598 exc.allowed_prefixes = allowed_prefixes
594 599 raise exc
595 600
596 601 elif repo_type == 'git':
597 602 allowed_prefixes = ('http', 'svn+http', 'hg+http')
598 603 if 'http' in url[:4]:
599 604 # initially check if it's at least the proper URL
600 605 # or does it pass basic auth
601 606 GitRepository.check_url(url, config)
602 607 elif 'svn+http' in url[:8]: # svn->git import
603 608 raise NotImplementedError()
604 609 elif 'hg+http' in url[:8]: # hg->git import
605 610 raise NotImplementedError()
606 611 else:
607 612 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
608 613 'Allowed url must start with one of %s'
609 614 % (url, ','.join(allowed_prefixes)))
610 615 exc.allowed_prefixes = allowed_prefixes
611 616 raise exc
612 617
613 618 class _validator(formencode.validators.FancyValidator):
614 619 messages = {
615 620 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
616 621 'invalid_clone_uri': _(
617 622 u'Invalid clone url, provide a valid clone '
618 623 u'url starting with one of %(allowed_prefixes)s')
619 624 }
620 625
621 626 def validate_python(self, value, state):
622 627 repo_type = value.get('repo_type')
623 628 url = value.get('clone_uri')
624 629
625 630 if url:
626 631 try:
627 632 url_handler(repo_type, url)
628 633 except InvalidCloneUrl as e:
629 634 log.warning(e)
630 635 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
631 636 allowed_prefixes=','.join(e.allowed_prefixes))
632 637 raise formencode.Invalid(msg, value, state,
633 638 error_dict={'clone_uri': msg})
634 639 except Exception:
635 640 log.exception('Url validation failed')
636 641 msg = M(self, 'clone_uri', rtype=repo_type)
637 642 raise formencode.Invalid(msg, value, state,
638 643 error_dict={'clone_uri': msg})
639 644 return _validator
640 645
641 646
642 647 def ValidForkType(old_data={}):
643 648 class _validator(formencode.validators.FancyValidator):
644 649 messages = {
645 650 'invalid_fork_type': _(u'Fork have to be the same type as parent')
646 651 }
647 652
648 653 def validate_python(self, value, state):
649 654 if old_data['repo_type'] != value:
650 655 msg = M(self, 'invalid_fork_type', state)
651 656 raise formencode.Invalid(
652 657 msg, value, state, error_dict={'repo_type': msg}
653 658 )
654 659 return _validator
655 660
656 661
657 662 def CanWriteGroup(old_data=None):
658 663 class _validator(formencode.validators.FancyValidator):
659 664 messages = {
660 665 'permission_denied': _(
661 666 u"You do not have the permission "
662 667 u"to create repositories in this group."),
663 668 'permission_denied_root': _(
664 669 u"You do not have the permission to store repositories in "
665 670 u"the root location.")
666 671 }
667 672
668 673 def _to_python(self, value, state):
669 674 # root location
670 675 if value in [-1, "-1"]:
671 676 return None
672 677 return value
673 678
674 679 def validate_python(self, value, state):
675 680 gr = RepoGroup.get(value)
676 681 gr_name = gr.group_name if gr else None # None means ROOT location
677 682 # create repositories with write permission on group is set to true
678 683 create_on_write = HasPermissionAny(
679 684 'hg.create.write_on_repogroup.true')()
680 685 group_admin = HasRepoGroupPermissionAny('group.admin')(
681 686 gr_name, 'can write into group validator')
682 687 group_write = HasRepoGroupPermissionAny('group.write')(
683 688 gr_name, 'can write into group validator')
684 689 forbidden = not (group_admin or (group_write and create_on_write))
685 690 can_create_repos = HasPermissionAny(
686 691 'hg.admin', 'hg.create.repository')
687 692 gid = (old_data['repo_group'].get('group_id')
688 693 if (old_data and 'repo_group' in old_data) else None)
689 694 value_changed = gid != safe_int(value)
690 695 new = not old_data
691 696 # do check if we changed the value, there's a case that someone got
692 697 # revoked write permissions to a repository, he still created, we
693 698 # don't need to check permission if he didn't change the value of
694 699 # groups in form box
695 700 if value_changed or new:
696 701 # parent group need to be existing
697 702 if gr and forbidden:
698 703 msg = M(self, 'permission_denied', state)
699 704 raise formencode.Invalid(
700 705 msg, value, state, error_dict={'repo_type': msg}
701 706 )
702 707 # check if we can write to root location !
703 708 elif gr is None and not can_create_repos():
704 709 msg = M(self, 'permission_denied_root', state)
705 710 raise formencode.Invalid(
706 711 msg, value, state, error_dict={'repo_type': msg}
707 712 )
708 713
709 714 return _validator
710 715
711 716
712 717 def ValidPerms(type_='repo'):
713 718 if type_ == 'repo_group':
714 719 EMPTY_PERM = 'group.none'
715 720 elif type_ == 'repo':
716 721 EMPTY_PERM = 'repository.none'
717 722 elif type_ == 'user_group':
718 723 EMPTY_PERM = 'usergroup.none'
719 724
720 725 class _validator(formencode.validators.FancyValidator):
721 726 messages = {
722 727 'perm_new_member_name':
723 728 _(u'This username or user group name is not valid')
724 729 }
725 730
726 731 def _to_python(self, value, state):
727 732 perm_updates = OrderedSet()
728 733 perm_additions = OrderedSet()
729 734 perm_deletions = OrderedSet()
730 735 # build a list of permission to update/delete and new permission
731 736
732 737 # Read the perm_new_member/perm_del_member attributes and group
733 738 # them by they IDs
734 739 new_perms_group = defaultdict(dict)
735 740 del_perms_group = defaultdict(dict)
736 741 for k, v in value.copy().iteritems():
737 742 if k.startswith('perm_del_member'):
738 743 # delete from org storage so we don't process that later
739 744 del value[k]
740 745 # part is `id`, `type`
741 746 _type, part = k.split('perm_del_member_')
742 747 args = part.split('_')
743 748 if len(args) == 2:
744 749 _key, pos = args
745 750 del_perms_group[pos][_key] = v
746 751 if k.startswith('perm_new_member'):
747 752 # delete from org storage so we don't process that later
748 753 del value[k]
749 754 # part is `id`, `type`, `perm`
750 755 _type, part = k.split('perm_new_member_')
751 756 args = part.split('_')
752 757 if len(args) == 2:
753 758 _key, pos = args
754 759 new_perms_group[pos][_key] = v
755 760
756 761 # store the deletes
757 762 for k in sorted(del_perms_group.keys()):
758 763 perm_dict = del_perms_group[k]
759 764 del_member = perm_dict.get('id')
760 765 del_type = perm_dict.get('type')
761 766 if del_member and del_type:
762 767 perm_deletions.add((del_member, None, del_type))
763 768
764 769 # store additions in order of how they were added in web form
765 770 for k in sorted(new_perms_group.keys()):
766 771 perm_dict = new_perms_group[k]
767 772 new_member = perm_dict.get('id')
768 773 new_type = perm_dict.get('type')
769 774 new_perm = perm_dict.get('perm')
770 775 if new_member and new_perm and new_type:
771 776 perm_additions.add((new_member, new_perm, new_type))
772 777
773 778 # get updates of permissions
774 779 # (read the existing radio button states)
775 780 for k, update_value in value.iteritems():
776 781 if k.startswith('u_perm_') or k.startswith('g_perm_'):
777 782 member = k[7:]
778 783 update_type = {'u': 'user',
779 784 'g': 'users_group'}[k[0]]
780 785 if member == User.DEFAULT_USER:
781 786 if str2bool(value.get('repo_private')):
782 787 # set none for default when updating to
783 788 # private repo protects agains form manipulation
784 789 update_value = EMPTY_PERM
785 790 perm_updates.add((member, update_value, update_type))
786 791 # check the deletes
787 792
788 793 value['perm_additions'] = list(perm_additions)
789 794 value['perm_updates'] = list(perm_updates)
790 795 value['perm_deletions'] = list(perm_deletions)
791 796
792 797 # validate users they exist and they are active !
793 798 for member_id, _perm, member_type in perm_additions:
794 799 try:
795 800 if member_type == 'user':
796 801 self.user_db = User.query()\
797 802 .filter(User.active == true())\
798 803 .filter(User.user_id == member_id).one()
799 804 if member_type == 'users_group':
800 805 self.user_db = UserGroup.query()\
801 806 .filter(UserGroup.users_group_active == true())\
802 807 .filter(UserGroup.users_group_id == member_id)\
803 808 .one()
804 809
805 810 except Exception:
806 811 log.exception('Updated permission failed: org_exc:')
807 812 msg = M(self, 'perm_new_member_type', state)
808 813 raise formencode.Invalid(
809 814 msg, value, state, error_dict={
810 815 'perm_new_member_name': msg}
811 816 )
812 817 return value
813 818 return _validator
814 819
815 820
816 821 def ValidSettings():
817 822 class _validator(formencode.validators.FancyValidator):
818 823 def _to_python(self, value, state):
819 824 # settings form for users that are not admin
820 825 # can't edit certain parameters, it's extra backup if they mangle
821 826 # with forms
822 827
823 828 forbidden_params = [
824 829 'user', 'repo_type', 'repo_enable_locking',
825 830 'repo_enable_downloads', 'repo_enable_statistics'
826 831 ]
827 832
828 833 for param in forbidden_params:
829 834 if param in value:
830 835 del value[param]
831 836 return value
832 837
833 838 def validate_python(self, value, state):
834 839 pass
835 840 return _validator
836 841
837 842
838 843 def ValidPath():
839 844 class _validator(formencode.validators.FancyValidator):
840 845 messages = {
841 846 'invalid_path': _(u'This is not a valid path')
842 847 }
843 848
844 849 def validate_python(self, value, state):
845 850 if not os.path.isdir(value):
846 851 msg = M(self, 'invalid_path', state)
847 852 raise formencode.Invalid(
848 853 msg, value, state, error_dict={'paths_root_path': msg}
849 854 )
850 855 return _validator
851 856
852 857
853 858 def UniqSystemEmail(old_data={}):
854 859 class _validator(formencode.validators.FancyValidator):
855 860 messages = {
856 861 'email_taken': _(u'This e-mail address is already taken')
857 862 }
858 863
859 864 def _to_python(self, value, state):
860 865 return value.lower()
861 866
862 867 def validate_python(self, value, state):
863 868 if (old_data.get('email') or '').lower() != value:
864 869 user = User.get_by_email(value, case_insensitive=True)
865 870 if user:
866 871 msg = M(self, 'email_taken', state)
867 872 raise formencode.Invalid(
868 873 msg, value, state, error_dict={'email': msg}
869 874 )
870 875 return _validator
871 876
872 877
873 878 def ValidSystemEmail():
874 879 class _validator(formencode.validators.FancyValidator):
875 880 messages = {
876 881 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
877 882 }
878 883
879 884 def _to_python(self, value, state):
880 885 return value.lower()
881 886
882 887 def validate_python(self, value, state):
883 888 user = User.get_by_email(value, case_insensitive=True)
884 889 if user is None:
885 890 msg = M(self, 'non_existing_email', state, email=value)
886 891 raise formencode.Invalid(
887 892 msg, value, state, error_dict={'email': msg}
888 893 )
889 894
890 895 return _validator
891 896
892 897
893 898 def NotReviewedRevisions(repo_id):
894 899 class _validator(formencode.validators.FancyValidator):
895 900 messages = {
896 901 'rev_already_reviewed':
897 902 _(u'Revisions %(revs)s are already part of pull request '
898 903 u'or have set status'),
899 904 }
900 905
901 906 def validate_python(self, value, state):
902 907 # check revisions if they are not reviewed, or a part of another
903 908 # pull request
904 909 statuses = ChangesetStatus.query()\
905 910 .filter(ChangesetStatus.revision.in_(value))\
906 911 .filter(ChangesetStatus.repo_id == repo_id)\
907 912 .all()
908 913
909 914 errors = []
910 915 for status in statuses:
911 916 if status.pull_request_id:
912 917 errors.append(['pull_req', status.revision[:12]])
913 918 elif status.status:
914 919 errors.append(['status', status.revision[:12]])
915 920
916 921 if errors:
917 922 revs = ','.join([x[1] for x in errors])
918 923 msg = M(self, 'rev_already_reviewed', state, revs=revs)
919 924 raise formencode.Invalid(
920 925 msg, value, state, error_dict={'revisions': revs})
921 926
922 927 return _validator
923 928
924 929
925 930 def ValidIp():
926 931 class _validator(CIDR):
927 932 messages = {
928 933 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
929 934 'illegalBits': _(
930 935 u'The network size (bits) must be within the range '
931 936 u'of 0-32 (not %(bits)r)'),
932 937 }
933 938
934 939 # we ovveride the default to_python() call
935 940 def to_python(self, value, state):
936 941 v = super(_validator, self).to_python(value, state)
937 942 v = v.strip()
938 943 net = ipaddress.ip_network(address=v, strict=False)
939 944 return str(net)
940 945
941 946 def validate_python(self, value, state):
942 947 try:
943 948 addr = value.strip()
944 949 # this raises an ValueError if address is not IpV4 or IpV6
945 950 ipaddress.ip_network(addr, strict=False)
946 951 except ValueError:
947 952 raise formencode.Invalid(self.message('badFormat', state),
948 953 value, state)
949 954
950 955 return _validator
951 956
952 957
953 958 def FieldKey():
954 959 class _validator(formencode.validators.FancyValidator):
955 960 messages = {
956 961 'badFormat': _(
957 962 u'Key name can only consist of letters, '
958 963 u'underscore, dash or numbers'),
959 964 }
960 965
961 966 def validate_python(self, value, state):
962 967 if not re.match('[a-zA-Z0-9_-]+$', value):
963 968 raise formencode.Invalid(self.message('badFormat', state),
964 969 value, state)
965 970 return _validator
966 971
967 972
968 973 def BasePath():
969 974 class _validator(formencode.validators.FancyValidator):
970 975 messages = {
971 976 'badPath': _(u'Filename cannot be inside a directory'),
972 977 }
973 978
974 979 def _to_python(self, value, state):
975 980 return value
976 981
977 982 def validate_python(self, value, state):
978 983 if value != os.path.basename(value):
979 984 raise formencode.Invalid(self.message('badPath', state),
980 985 value, state)
981 986 return _validator
982 987
983 988
984 989 def ValidAuthPlugins():
985 990 class _validator(formencode.validators.FancyValidator):
986 991 messages = {
987 992 'import_duplicate': _(
988 993 u'Plugins %(loaded)s and %(next_to_load)s '
989 994 u'both export the same name'),
990 995 'missing_includeme': _(
991 996 u'The plugin "%(plugin_id)s" is missing an includeme '
992 997 u'function.'),
993 998 'import_error': _(
994 999 u'Can not load plugin "%(plugin_id)s"'),
995 1000 'no_plugin': _(
996 1001 u'No plugin available with ID "%(plugin_id)s"'),
997 1002 }
998 1003
999 1004 def _to_python(self, value, state):
1000 1005 # filter empty values
1001 1006 return filter(lambda s: s not in [None, ''], value)
1002 1007
1003 1008 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1004 1009 """
1005 1010 Validates that the plugin import works. It also checks that the
1006 1011 plugin has an includeme attribute.
1007 1012 """
1008 1013 try:
1009 1014 plugin = _import_legacy_plugin(plugin_id)
1010 1015 except Exception as e:
1011 1016 log.exception(
1012 1017 'Exception during import of auth legacy plugin "{}"'
1013 1018 .format(plugin_id))
1014 1019 msg = M(self, 'import_error', plugin_id=plugin_id)
1015 1020 raise formencode.Invalid(msg, value, state)
1016 1021
1017 1022 if not hasattr(plugin, 'includeme'):
1018 1023 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1019 1024 raise formencode.Invalid(msg, value, state)
1020 1025
1021 1026 return plugin
1022 1027
1023 1028 def _validate_plugin_id(self, plugin_id, value, state):
1024 1029 """
1025 1030 Plugins are already imported during app start up. Therefore this
1026 1031 validation only retrieves the plugin from the plugin registry and
1027 1032 if it returns something not None everything is OK.
1028 1033 """
1029 1034 plugin = loadplugin(plugin_id)
1030 1035
1031 1036 if plugin is None:
1032 1037 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1033 1038 raise formencode.Invalid(msg, value, state)
1034 1039
1035 1040 return plugin
1036 1041
1037 1042 def validate_python(self, value, state):
1038 1043 unique_names = {}
1039 1044 for plugin_id in value:
1040 1045
1041 1046 # Validate legacy or normal plugin.
1042 1047 if plugin_id.startswith(legacy_plugin_prefix):
1043 1048 plugin = self._validate_legacy_plugin_id(
1044 1049 plugin_id, value, state)
1045 1050 else:
1046 1051 plugin = self._validate_plugin_id(plugin_id, value, state)
1047 1052
1048 1053 # Only allow unique plugin names.
1049 1054 if plugin.name in unique_names:
1050 1055 msg = M(self, 'import_duplicate', state,
1051 1056 loaded=unique_names[plugin.name],
1052 1057 next_to_load=plugin)
1053 1058 raise formencode.Invalid(msg, value, state)
1054 1059 unique_names[plugin.name] = plugin
1055 1060
1056 1061 return _validator
1057 1062
1058 1063
1059 1064 def UniqGistId():
1060 1065 class _validator(formencode.validators.FancyValidator):
1061 1066 messages = {
1062 1067 'gistid_taken': _(u'This gistid is already in use')
1063 1068 }
1064 1069
1065 1070 def _to_python(self, value, state):
1066 1071 return repo_name_slug(value.lower())
1067 1072
1068 1073 def validate_python(self, value, state):
1069 1074 existing = Gist.get_by_access_id(value)
1070 1075 if existing:
1071 1076 msg = M(self, 'gistid_taken', state)
1072 1077 raise formencode.Invalid(
1073 1078 msg, value, state, error_dict={'gistid': msg}
1074 1079 )
1075 1080
1076 1081 return _validator
1077 1082
1078 1083
1079 1084 def ValidPattern():
1080 1085
1081 1086 class _Validator(formencode.validators.FancyValidator):
1082 1087
1083 1088 def _to_python(self, value, state):
1084 1089 patterns = []
1085 1090
1086 1091 prefix = 'new_pattern'
1087 1092 for name, v in value.iteritems():
1088 1093 pattern_name = '_'.join((prefix, 'pattern'))
1089 1094 if name.startswith(pattern_name):
1090 1095 new_item_id = name[len(pattern_name)+1:]
1091 1096
1092 1097 def _field(name):
1093 1098 return '%s_%s_%s' % (prefix, name, new_item_id)
1094 1099
1095 1100 values = {
1096 1101 'issuetracker_pat': value.get(_field('pattern')),
1097 1102 'issuetracker_pat': value.get(_field('pattern')),
1098 1103 'issuetracker_url': value.get(_field('url')),
1099 1104 'issuetracker_pref': value.get(_field('prefix')),
1100 1105 'issuetracker_desc': value.get(_field('description'))
1101 1106 }
1102 1107 new_uid = md5(values['issuetracker_pat'])
1103 1108
1104 1109 has_required_fields = (
1105 1110 values['issuetracker_pat']
1106 1111 and values['issuetracker_url'])
1107 1112
1108 1113 if has_required_fields:
1109 1114 settings = [
1110 1115 ('_'.join((key, new_uid)), values[key], 'unicode')
1111 1116 for key in values]
1112 1117 patterns.append(settings)
1113 1118
1114 1119 value['patterns'] = patterns
1115 1120 delete_patterns = value.get('uid') or []
1116 1121 if not isinstance(delete_patterns, (list, tuple)):
1117 1122 delete_patterns = [delete_patterns]
1118 1123 value['delete_patterns'] = delete_patterns
1119 1124 return value
1120 1125 return _Validator
General Comments 0
You need to be logged in to leave comments. Login now