##// END OF EJS Templates
repo groups: make it possible to remove own explicit permissions, now when group owners always have admin permissions...
Mads Kiilerich -
r8772:2e1059de stable
parent child Browse files
Show More
@@ -1,403 +1,386 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.repo_groups
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Repository groups controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Mar 23, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30
31 31 import formencode
32 32 from formencode import htmlfill
33 33 from tg import app_globals, request
34 34 from tg import tmpl_context as c
35 35 from tg.i18n import ugettext as _
36 36 from tg.i18n import ungettext
37 37 from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
38 38
39 39 from kallithea.controllers import base
40 40 from kallithea.lib import webutils
41 41 from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoGroupPermissionLevelDecorator, LoginRequired
42 42 from kallithea.lib.utils2 import safe_int
43 43 from kallithea.lib.webutils import url
44 44 from kallithea.model import db, meta
45 45 from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm
46 46 from kallithea.model.repo import RepoModel
47 47 from kallithea.model.repo_group import RepoGroupModel
48 48 from kallithea.model.scm import AvailableRepoGroupChoices, RepoGroupList
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class RepoGroupsController(base.BaseController):
55 55
56 56 @LoginRequired(allow_default_user=True)
57 57 def _before(self, *args, **kwargs):
58 58 super(RepoGroupsController, self)._before(*args, **kwargs)
59 59
60 60 def __load_defaults(self, extras=(), exclude=()):
61 61 """extras is used for keeping current parent ignoring permissions
62 62 exclude is used for not moving group to itself TODO: also exclude descendants
63 63 Note: only admin can create top level groups
64 64 """
65 65 repo_groups = AvailableRepoGroupChoices('admin', extras)
66 66 exclude_group_ids = set(rg.group_id for rg in exclude)
67 67 c.repo_groups = [rg for rg in repo_groups
68 68 if rg[0] not in exclude_group_ids]
69 69
70 70 def __load_data(self, group_id):
71 71 """
72 72 Load defaults settings for edit, and update
73 73
74 74 :param group_id:
75 75 """
76 76 repo_group = db.RepoGroup.get_or_404(group_id)
77 77 data = repo_group.get_dict()
78 78 data['group_name'] = repo_group.name
79 79 data['owner'] = repo_group.owner.username
80 80
81 81 # fill repository group users
82 82 for p in repo_group.repo_group_to_perm:
83 83 data.update({'u_perm_%s' % p.user.username:
84 84 p.permission.permission_name})
85 85
86 86 # fill repository group groups
87 87 for p in repo_group.users_group_to_perm:
88 88 data.update({'g_perm_%s' % p.users_group.users_group_name:
89 89 p.permission.permission_name})
90 90
91 91 return data
92 92
93 def _revoke_perms_on_yourself(self, form_result):
94 _up = [u for u in form_result['perms_updates'] if request.authuser.username == u[0]]
95 _new = [u for u in form_result['perms_new'] if request.authuser.username == u[0]]
96 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
97 return True
98 return False
99
100 93 def index(self, format='html'):
101 94 _list = db.RepoGroup.query(sorted=True).all()
102 95 group_iter = RepoGroupList(_list, perm_level='admin')
103 96 repo_groups_data = []
104 97 _tmpl_lookup = app_globals.mako_lookup
105 98 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
106 99
107 100 def repo_group_name(repo_group_name, children_groups):
108 101 return template.get_def("repo_group_name") \
109 102 .render_unicode(repo_group_name, children_groups, _=_, webutils=webutils, c=c)
110 103
111 104 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
112 105 return template.get_def("repo_group_actions") \
113 106 .render_unicode(repo_group_id, repo_group_name, gr_count, _=_, webutils=webutils, c=c,
114 107 ungettext=ungettext)
115 108
116 109 for repo_gr in group_iter:
117 110 children_groups = [g.name for g in repo_gr.parents] + [repo_gr.name]
118 111 repo_count = repo_gr.repositories.count()
119 112 repo_groups_data.append({
120 113 "raw_name": webutils.escape(repo_gr.group_name),
121 114 "group_name": repo_group_name(repo_gr.group_name, children_groups),
122 115 "desc": webutils.escape(repo_gr.group_description),
123 116 "repos": repo_count,
124 117 "owner": repo_gr.owner.username,
125 118 "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
126 119 repo_count)
127 120 })
128 121
129 122 c.data = {
130 123 "sort": None,
131 124 "dir": "asc",
132 125 "records": repo_groups_data
133 126 }
134 127
135 128 return base.render('admin/repo_groups/repo_groups.html')
136 129
137 130 def create(self):
138 131 self.__load_defaults()
139 132
140 133 # permissions for can create group based on parent_id are checked
141 134 # here in the Form
142 135 repo_group_form = RepoGroupForm(repo_groups=c.repo_groups)
143 136 form_result = None
144 137 try:
145 138 form_result = repo_group_form.to_python(dict(request.POST))
146 139 gr = RepoGroupModel().create(
147 140 group_name=form_result['group_name'],
148 141 group_description=form_result['group_description'],
149 142 parent=form_result['parent_group_id'],
150 143 owner=request.authuser.user_id,
151 144 copy_permissions=form_result['group_copy_permissions']
152 145 )
153 146 meta.Session().commit()
154 147 # TODO: in future action_logger(, '', '', '')
155 148 except formencode.Invalid as errors:
156 149 return htmlfill.render(
157 150 base.render('admin/repo_groups/repo_group_add.html'),
158 151 defaults=errors.value,
159 152 errors=errors.error_dict or {},
160 153 prefix_error=False,
161 154 encoding="UTF-8",
162 155 force_defaults=False)
163 156 except Exception:
164 157 log.error(traceback.format_exc())
165 158 webutils.flash(_('Error occurred during creation of repository group %s')
166 159 % request.POST.get('group_name'), category='error')
167 160 if form_result is None:
168 161 raise
169 162 parent_group_id = form_result['parent_group_id']
170 163 # TODO: maybe we should get back to the main view, not the admin one
171 164 raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id))
172 165 webutils.flash(_('Created repository group %s') % gr.group_name,
173 166 category='success')
174 167 raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name))
175 168
176 169 def new(self):
177 170 parent_group_id = safe_int(request.GET.get('parent_group') or '-1')
178 171 if HasPermissionAny('hg.admin')('group create'):
179 172 # we're global admin, we're ok and we can create TOP level groups
180 173 pass
181 174 else:
182 175 # we pass in parent group into creation form, thus we know
183 176 # what would be the group, we can check perms here !
184 177 group = db.RepoGroup.get(parent_group_id) if parent_group_id else None
185 178 group_name = group.group_name if group else None
186 179 if HasRepoGroupPermissionLevel('admin')(group_name, 'group create'):
187 180 pass
188 181 else:
189 182 raise HTTPForbidden()
190 183
191 184 self.__load_defaults()
192 185 return htmlfill.render(
193 186 base.render('admin/repo_groups/repo_group_add.html'),
194 187 defaults={'parent_group_id': parent_group_id},
195 188 errors={},
196 189 prefix_error=False,
197 190 encoding="UTF-8",
198 191 force_defaults=False)
199 192
200 193 @HasRepoGroupPermissionLevelDecorator('admin')
201 194 def update(self, group_name):
202 195 c.repo_group = db.RepoGroup.guess_instance(group_name)
203 196 self.__load_defaults(extras=[c.repo_group.parent_group],
204 197 exclude=[c.repo_group])
205 198
206 199 # TODO: kill allow_empty_group - it is only used for redundant form validation!
207 200 if HasPermissionAny('hg.admin')('group edit'):
208 201 # we're global admin, we're ok and we can create TOP level groups
209 202 allow_empty_group = True
210 203 elif not c.repo_group.parent_group:
211 204 allow_empty_group = True
212 205 else:
213 206 allow_empty_group = False
214 207 repo_group_form = RepoGroupForm(
215 208 edit=True,
216 209 old_data=c.repo_group.get_dict(),
217 210 repo_groups=c.repo_groups,
218 211 can_create_in_root=allow_empty_group,
219 212 )()
220 213 try:
221 214 form_result = repo_group_form.to_python(dict(request.POST))
222 215
223 216 new_gr = RepoGroupModel().update(group_name, form_result)
224 217 meta.Session().commit()
225 218 webutils.flash(_('Updated repository group %s')
226 219 % form_result['group_name'], category='success')
227 220 # we now have new name !
228 221 group_name = new_gr.group_name
229 222 # TODO: in future action_logger(, '', '', '')
230 223 except formencode.Invalid as errors:
231 224 c.active = 'settings'
232 225 return htmlfill.render(
233 226 base.render('admin/repo_groups/repo_group_edit.html'),
234 227 defaults=errors.value,
235 228 errors=errors.error_dict or {},
236 229 prefix_error=False,
237 230 encoding="UTF-8",
238 231 force_defaults=False)
239 232 except Exception:
240 233 log.error(traceback.format_exc())
241 234 webutils.flash(_('Error occurred during update of repository group %s')
242 235 % request.POST.get('group_name'), category='error')
243 236
244 237 raise HTTPFound(location=url('edit_repo_group', group_name=group_name))
245 238
246 239 @HasRepoGroupPermissionLevelDecorator('admin')
247 240 def delete(self, group_name):
248 241 gr = c.repo_group = db.RepoGroup.guess_instance(group_name)
249 242 parent_group = gr.parent_group
250 243 repos = gr.repositories.all()
251 244 if repos:
252 245 webutils.flash(_('This group contains %s repositories and cannot be '
253 246 'deleted') % len(repos), category='warning')
254 247 raise HTTPFound(location=url('repos_groups'))
255 248
256 249 children = gr.children.all()
257 250 if children:
258 251 webutils.flash(_('This group contains %s subgroups and cannot be deleted'
259 252 % (len(children))), category='warning')
260 253 raise HTTPFound(location=url('repos_groups'))
261 254
262 255 try:
263 256 RepoGroupModel().delete(group_name)
264 257 meta.Session().commit()
265 258 webutils.flash(_('Removed repository group %s') % group_name,
266 259 category='success')
267 260 # TODO: in future action_logger(, '', '', '')
268 261 except Exception:
269 262 log.error(traceback.format_exc())
270 263 webutils.flash(_('Error occurred during deletion of repository group %s')
271 264 % group_name, category='error')
272 265
273 266 if parent_group:
274 267 raise HTTPFound(location=url('repos_group_home', group_name=parent_group.group_name))
275 268 raise HTTPFound(location=url('repos_groups'))
276 269
277 270 def show_by_name(self, group_name):
278 271 """
279 272 This is a proxy that does a lookup group_name -> id, and shows
280 273 the group by id view instead
281 274 """
282 275 group_name = group_name.rstrip('/')
283 276 id_ = db.RepoGroup.get_by_group_name(group_name)
284 277 if id_:
285 278 return self.show(group_name)
286 279 raise HTTPNotFound
287 280
288 281 @HasRepoGroupPermissionLevelDecorator('read')
289 282 def show(self, group_name):
290 283 c.active = 'settings'
291 284
292 285 c.group = c.repo_group = db.RepoGroup.guess_instance(group_name)
293 286
294 287 groups = db.RepoGroup.query(sorted=True).filter_by(parent_group=c.group).all()
295 288 repo_groups_list = self.scm_model.get_repo_groups(groups)
296 289
297 290 repos_list = db.Repository.query(sorted=True).filter_by(group=c.group).all()
298 291 c.data = RepoModel().get_repos_as_dict(repos_list,
299 292 repo_groups_list=repo_groups_list,
300 293 short_name=True)
301 294
302 295 return base.render('admin/repo_groups/repo_group_show.html')
303 296
304 297 @HasRepoGroupPermissionLevelDecorator('admin')
305 298 def edit(self, group_name):
306 299 c.active = 'settings'
307 300
308 301 c.repo_group = db.RepoGroup.guess_instance(group_name)
309 302 self.__load_defaults(extras=[c.repo_group.parent_group],
310 303 exclude=[c.repo_group])
311 304 defaults = self.__load_data(c.repo_group.group_id)
312 305
313 306 return htmlfill.render(
314 307 base.render('admin/repo_groups/repo_group_edit.html'),
315 308 defaults=defaults,
316 309 encoding="UTF-8",
317 310 force_defaults=False
318 311 )
319 312
320 313 @HasRepoGroupPermissionLevelDecorator('admin')
321 314 def edit_repo_group_advanced(self, group_name):
322 315 c.active = 'advanced'
323 316 c.repo_group = db.RepoGroup.guess_instance(group_name)
324 317
325 318 return base.render('admin/repo_groups/repo_group_edit.html')
326 319
327 320 @HasRepoGroupPermissionLevelDecorator('admin')
328 321 def edit_repo_group_perms(self, group_name):
329 322 c.active = 'perms'
330 323 c.repo_group = db.RepoGroup.guess_instance(group_name)
331 324 self.__load_defaults()
332 325 defaults = self.__load_data(c.repo_group.group_id)
333 326
334 327 return htmlfill.render(
335 328 base.render('admin/repo_groups/repo_group_edit.html'),
336 329 defaults=defaults,
337 330 encoding="UTF-8",
338 331 force_defaults=False
339 332 )
340 333
341 334 @HasRepoGroupPermissionLevelDecorator('admin')
342 335 def update_perms(self, group_name):
343 336 """
344 337 Update permissions for given repository group
345 338
346 339 :param group_name:
347 340 """
348 341
349 342 c.repo_group = db.RepoGroup.guess_instance(group_name)
350 343 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
351 344 form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST)
352 if not request.authuser.is_admin:
353 if self._revoke_perms_on_yourself(form_result):
354 msg = _('Cannot revoke permission for yourself as admin')
355 webutils.flash(msg, category='warning')
356 raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
357 345 recursive = form_result['recursive']
358 346 # iterate over all members(if in recursive mode) of this groups and
359 347 # set the permissions !
360 348 # this can be potentially heavy operation
361 349 RepoGroupModel()._update_permissions(c.repo_group,
362 350 form_result['perms_new'],
363 351 form_result['perms_updates'],
364 352 recursive)
365 353 # TODO: implement this
366 354 #action_logger(request.authuser, 'admin_changed_repo_permissions',
367 355 # repo_name, request.ip_addr)
368 356 meta.Session().commit()
369 357 webutils.flash(_('Repository group permissions updated'), category='success')
370 358 raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
371 359
372 360 @HasRepoGroupPermissionLevelDecorator('admin')
373 361 def delete_perms(self, group_name):
374 362 try:
375 363 obj_type = request.POST.get('obj_type')
376 364 obj_id = None
377 365 if obj_type == 'user':
378 366 obj_id = safe_int(request.POST.get('user_id'))
379 367 elif obj_type == 'user_group':
380 368 obj_id = safe_int(request.POST.get('user_group_id'))
381 369
382 if not request.authuser.is_admin:
383 if obj_type == 'user' and request.authuser.user_id == obj_id:
384 msg = _('Cannot revoke permission for yourself as admin')
385 webutils.flash(msg, category='warning')
386 raise Exception('revoke admin permission on self')
387 370 recursive = request.POST.get('recursive', 'none')
388 371 if obj_type == 'user':
389 372 RepoGroupModel().delete_permission(repo_group=group_name,
390 373 obj=obj_id, obj_type='user',
391 374 recursive=recursive)
392 375 elif obj_type == 'user_group':
393 376 RepoGroupModel().delete_permission(repo_group=group_name,
394 377 obj=obj_id,
395 378 obj_type='user_group',
396 379 recursive=recursive)
397 380
398 381 meta.Session().commit()
399 382 except Exception:
400 383 log.error(traceback.format_exc())
401 384 webutils.flash(_('An error occurred during revoking of permission'),
402 385 category='error')
403 386 raise HTTPInternalServerError()
@@ -1,122 +1,109 b''
1 1 ${h.form(url('edit_repo_group_perms', group_name=c.repo_group.group_name))}
2 2 <div class="form">
3 3 <div>
4 4 <table id="permissions_manage" class="table">
5 5 <tr>
6 6 <td>${_('None')}<br />(${_('Not visible')})</td>
7 7 <td>${_('Read')}<br />(${_('Visible')})</td>
8 8 <td>${_('Write')}<br />(${_('Add repos')})</td>
9 9 <td>${_('Admin')}<br />(${_('Add/Edit groups')})</td>
10 10 <td>${_('User/User Group')}</td>
11 11 <td></td>
12 12 </tr>
13 13 ## USERS
14 14 %for r2p in c.repo_group.repo_group_to_perm:
15 ##forbid revoking permission from yourself, except if you're an super admin
16 15 <tr id="id${id(r2p.user.username)}">
17 %if request.authuser.user_id != r2p.user.user_id or request.authuser.is_admin:
18 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
19 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
20 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
21 19 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
22 20 <td>
23 21 ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
24 22 %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
25 23 <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
26 24 %else:
27 25 ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
28 26 %endif
29 27 </td>
30 28 <td>
31 29 %if r2p.user.username !='default':
32 30 <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
33 31 <i class="icon-minus-circled"></i>${_('Revoke')}
34 32 </button>
35 33 %endif
36 34 </td>
37 %else:
38 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
39 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
40 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
41 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
42 <td>
43 ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
44 ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
45 </td>
46 <td><i class="icon-user"></i>${_('Admin')}</td>
47 %endif
48 35 </tr>
49 36 %endfor
50 37
51 38 ## USER GROUPS
52 39 %for g2p in c.repo_group.users_group_to_perm:
53 40 <tr id="id${id(g2p.users_group.users_group_name)}">
54 41 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
55 42 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
56 43 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
57 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
58 45 <td>
59 46 <i class="icon-users"></i>
60 47 %if h.HasPermissionAny('hg.admin')():
61 48 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">
62 49 ${g2p.users_group.users_group_name}
63 50 </a>
64 51 %else:
65 52 ${g2p.users_group.users_group_name}
66 53 %endif
67 54 </td>
68 55 <td>
69 56 <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
70 57 <i class="icon-minus-circled"></i>${_('Revoke')}
71 58 </button>
72 59 </td>
73 60 </tr>
74 61 %endfor
75 62 ## New entries added by addPermAction here.
76 63 <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
77 64 <tr>
78 65 <td colspan="6">
79 66 <button type="button" id="add_perm" class="btn btn-link btn-xs">
80 67 <i class="icon-plus"></i>${_('Add new')}
81 68 </button>
82 69 </td>
83 70 </tr>
84 71 <tr>
85 72 <td colspan="6">
86 73 ${_('Apply to children')}:
87 74 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
88 75 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
89 76 ${h.radio('recursive', 'repos', label=_('Repositories'))}
90 77 ${h.radio('recursive', 'all', label=_('Both'))}
91 78 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups if selected.')}</span>
92 79 </td>
93 80 </tr>
94 81 </table>
95 82 </div>
96 83 <div class="buttons">
97 84 ${h.submit('save',_('Save'),class_="btn btn-default")}
98 85 ${h.reset('reset',_('Reset'),class_="btn btn-default")}
99 86 </div>
100 87 </div>
101 88 ${h.end_form()}
102 89
103 90 <script>
104 91 'use strict';
105 92 function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
106 93 let url = ${h.jshtml(h.url('edit_repo_group_perms_delete', group_name=c.repo_group.group_name))};
107 94 var revoke_msg = _TM['Confirm to revoke permission for {0}: {1}?'].format(obj_type.replace('_', ' '), obj_name);
108 95 if (confirm(revoke_msg)){
109 96 var recursive = $('input[name=recursive]:checked').val();
110 97 ajaxActionRevokePermission(url, obj_id, obj_type, field_id, {recursive:recursive});
111 98 }
112 99 }
113 100
114 101 $(document).ready(function () {
115 102 if (!$('#perm_new_member_name').hasClass('error')) {
116 103 $('#add_perm_input').hide();
117 104 }
118 105 $('#add_perm').click(function () {
119 106 addPermAction('group');
120 107 });
121 108 });
122 109 </script>
General Comments 0
You need to be logged in to leave comments. Login now