##// END OF EJS Templates
user-groups: rewrote the app to pyramid...
marcink -
r2068:71f5d13f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,101 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
25
26
27 def admin_routes(config):
28 """
29 User groups /_admin prefixed routes
30 """
31
32 config.add_route(
33 name='user_group_members_data',
34 pattern='/user_groups/{user_group_id:\d+}/members',
35 user_group_route=True)
36
37 # user groups perms
38 config.add_route(
39 name='edit_user_group_perms_summary',
40 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary',
41 user_group_route=True)
42 config.add_route(
43 name='edit_user_group_perms_summary_json',
44 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json',
45 user_group_route=True)
46
47 # user groups edit
48 config.add_route(
49 name='edit_user_group',
50 pattern='/user_groups/{user_group_id:\d+}/edit',
51 user_group_route=True)
52
53 # user groups update
54 config.add_route(
55 name='user_groups_update',
56 pattern='/user_groups/{user_group_id:\d+}/update',
57 user_group_route=True)
58
59 config.add_route(
60 name='edit_user_group_global_perms',
61 pattern='/user_groups/{user_group_id:\d+}/edit/global_permissions',
62 user_group_route=True)
63
64 config.add_route(
65 name='edit_user_group_global_perms_update',
66 pattern='/user_groups/{user_group_id:\d+}/edit/global_permissions/update',
67 user_group_route=True)
68
69 config.add_route(
70 name='edit_user_group_perms',
71 pattern='/user_groups/{user_group_id:\d+}/edit/permissions',
72 user_group_route=True)
73
74 config.add_route(
75 name='edit_user_group_perms_update',
76 pattern='/user_groups/{user_group_id:\d+}/edit/permissions/update',
77 user_group_route=True)
78
79 config.add_route(
80 name='edit_user_group_advanced',
81 pattern='/user_groups/{user_group_id:\d+}/edit/advanced',
82 user_group_route=True)
83
84 config.add_route(
85 name='edit_user_group_advanced_sync',
86 pattern='/user_groups/{user_group_id:\d+}/edit/advanced/sync',
87 user_group_route=True)
88
89 # user groups delete
90 config.add_route(
91 name='user_groups_delete',
92 pattern='/user_groups/{user_group_id:\d+}/delete',
93 user_group_route=True)
94
95
96 def includeme(config):
97 # main admin routes
98 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
99
100 # Scan module for configuration decorators.
101 config.scan('.views', ignore='.tests')
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/\
This diff has been collapsed as it changes many lines, (529 lines changed) Show them Hide them
@@ -0,0 +1,529 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 import peppercorn
24 import formencode
25 import formencode.htmlfill
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
28 from pyramid.response import Response
29 from pyramid.renderers import render
30
31 from rhodecode.lib.exceptions import (
32 RepoGroupAssignmentError, UserGroupAssignedException)
33 from rhodecode.model.forms import (
34 UserGroupPermsForm, UserGroupForm, UserIndividualPermissionsForm,
35 UserPermissionsForm)
36 from rhodecode.model.permission import PermissionModel
37
38 from rhodecode.apps._base import UserGroupAppView
39 from rhodecode.lib.auth import (
40 LoginRequired, HasUserGroupPermissionAnyDecorator, CSRFRequired)
41 from rhodecode.lib import helpers as h, audit_logger
42 from rhodecode.lib.utils2 import str2bool
43 from rhodecode.model.db import (
44 joinedload, User, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
45 from rhodecode.model.meta import Session
46 from rhodecode.model.user_group import UserGroupModel
47
48 log = logging.getLogger(__name__)
49
50
51 class UserGroupsView(UserGroupAppView):
52
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
55
56 PermissionModel().set_global_permission_choices(
57 c, gettext_translator=self.request.translate)
58
59 self._register_global_c(c)
60 return c
61
62 def _get_perms_summary(self, user_group_id):
63 permissions = {
64 'repositories': {},
65 'repositories_groups': {},
66 }
67 ugroup_repo_perms = UserGroupRepoToPerm.query()\
68 .options(joinedload(UserGroupRepoToPerm.permission))\
69 .options(joinedload(UserGroupRepoToPerm.repository))\
70 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
71 .all()
72
73 for gr in ugroup_repo_perms:
74 permissions['repositories'][gr.repository.repo_name] \
75 = gr.permission.permission_name
76
77 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
78 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
79 .options(joinedload(UserGroupRepoGroupToPerm.group))\
80 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
81 .all()
82
83 for gr in ugroup_group_perms:
84 permissions['repositories_groups'][gr.group.group_name] \
85 = gr.permission.permission_name
86 return permissions
87
88 @LoginRequired()
89 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
90 @view_config(
91 route_name='user_group_members_data', request_method='GET',
92 renderer='json_ext', xhr=True)
93 def user_group_members(self):
94 """
95 Return members of given user group
96 """
97 user_group = self.db_user_group
98 group_members_obj = sorted((x.user for x in user_group.members),
99 key=lambda u: u.username.lower())
100
101 group_members = [
102 {
103 'id': user.user_id,
104 'first_name': user.first_name,
105 'last_name': user.last_name,
106 'username': user.username,
107 'icon_link': h.gravatar_url(user.email, 30),
108 'value_display': h.person(user.email),
109 'value': user.username,
110 'value_type': 'user',
111 'active': user.active,
112 }
113 for user in group_members_obj
114 ]
115
116 return {
117 'members': group_members
118 }
119
120 @LoginRequired()
121 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
122 @view_config(
123 route_name='edit_user_group_perms_summary', request_method='GET',
124 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
125 def user_group_perms_summary(self):
126 c = self.load_default_context()
127 c.user_group = self.db_user_group
128 c.active = 'perms_summary'
129 c.permissions = self._get_perms_summary(c.user_group.users_group_id)
130 return self._get_template_context(c)
131
132 @LoginRequired()
133 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
134 @view_config(
135 route_name='edit_user_group_perms_summary_json', request_method='GET',
136 renderer='json_ext')
137 def user_group_perms_summary_json(self):
138 self.load_default_context()
139 user_group = self.db_user_group
140 return self._get_perms_summary(user_group.users_group_id)
141
142 def _revoke_perms_on_yourself(self, form_result):
143 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
144 form_result['perm_updates'])
145 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
146 form_result['perm_additions'])
147 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
148 form_result['perm_deletions'])
149 admin_perm = 'usergroup.admin'
150 if _updates and _updates[0][1] != admin_perm or \
151 _additions and _additions[0][1] != admin_perm or \
152 _deletions and _deletions[0][1] != admin_perm:
153 return True
154 return False
155
156 @LoginRequired()
157 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
158 @CSRFRequired()
159 @view_config(
160 route_name='user_groups_update', request_method='POST',
161 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
162 def user_group_update(self):
163 _ = self.request.translate
164
165 user_group = self.db_user_group
166 user_group_id = user_group.users_group_id
167
168 c = self.load_default_context()
169 c.user_group = user_group
170 c.group_members_obj = [x.user for x in c.user_group.members]
171 c.group_members_obj.sort(key=lambda u: u.username.lower())
172 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
173 c.active = 'settings'
174
175 users_group_form = UserGroupForm(
176 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
177
178 old_values = c.user_group.get_api_data()
179 user_group_name = self.request.POST.get('users_group_name')
180 try:
181 form_result = users_group_form.to_python(self.request.POST)
182 pstruct = peppercorn.parse(self.request.POST.items())
183 form_result['users_group_members'] = pstruct['user_group_members']
184
185 user_group, added_members, removed_members = \
186 UserGroupModel().update(c.user_group, form_result)
187 updated_user_group = form_result['users_group_name']
188
189 audit_logger.store_web(
190 'user_group.edit', action_data={'old_data': old_values},
191 user=self._rhodecode_user)
192
193 # TODO(marcink): use added/removed to set user_group.edit.member.add
194
195 h.flash(_('Updated user group %s') % updated_user_group,
196 category='success')
197 Session().commit()
198 except formencode.Invalid as errors:
199 defaults = errors.value
200 e = errors.error_dict or {}
201
202 data = render(
203 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
204 self._get_template_context(c), self.request)
205 html = formencode.htmlfill.render(
206 data,
207 defaults=defaults,
208 errors=e,
209 prefix_error=False,
210 encoding="UTF-8",
211 force_defaults=False
212 )
213 return Response(html)
214
215 except Exception:
216 log.exception("Exception during update of user group")
217 h.flash(_('Error occurred during update of user group %s')
218 % user_group_name, category='error')
219
220 raise HTTPFound(
221 h.route_path('edit_user_group', user_group_id=user_group_id))
222
223 @LoginRequired()
224 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
225 @CSRFRequired()
226 @view_config(
227 route_name='user_groups_delete', request_method='POST',
228 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
229 def user_group_delete(self):
230 _ = self.request.translate
231 user_group = self.db_user_group
232
233 self.load_default_context()
234 force = str2bool(self.request.POST.get('force'))
235
236 old_values = user_group.get_api_data()
237 try:
238 UserGroupModel().delete(user_group, force=force)
239 audit_logger.store_web(
240 'user.delete', action_data={'old_data': old_values},
241 user=self._rhodecode_user)
242 Session().commit()
243 h.flash(_('Successfully deleted user group'), category='success')
244 except UserGroupAssignedException as e:
245 h.flash(str(e), category='error')
246 except Exception:
247 log.exception("Exception during deletion of user group")
248 h.flash(_('An error occurred during deletion of user group'),
249 category='error')
250 raise HTTPFound(h.route_path('user_groups'))
251
252 @LoginRequired()
253 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
254 @view_config(
255 route_name='edit_user_group', request_method='GET',
256 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
257 def user_group_edit(self):
258 user_group = self.db_user_group
259
260 c = self.load_default_context()
261 c.user_group = user_group
262 c.group_members_obj = [x.user for x in c.user_group.members]
263 c.group_members_obj.sort(key=lambda u: u.username.lower())
264 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
265
266 c.active = 'settings'
267
268 defaults = user_group.get_dict()
269 # fill owner
270 if user_group.user:
271 defaults.update({'user': user_group.user.username})
272 else:
273 replacement_user = User.get_first_super_admin().username
274 defaults.update({'user': replacement_user})
275
276 data = render(
277 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
278 self._get_template_context(c), self.request)
279 html = formencode.htmlfill.render(
280 data,
281 defaults=defaults,
282 encoding="UTF-8",
283 force_defaults=False
284 )
285 return Response(html)
286
287 @LoginRequired()
288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
289 @view_config(
290 route_name='edit_user_group_perms', request_method='GET',
291 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
292 def user_group_edit_perms(self):
293 user_group = self.db_user_group
294 c = self.load_default_context()
295 c.user_group = user_group
296 c.active = 'perms'
297
298 defaults = {}
299 # fill user group users
300 for p in c.user_group.user_user_group_to_perm:
301 defaults.update({'u_perm_%s' % p.user.user_id:
302 p.permission.permission_name})
303
304 for p in c.user_group.user_group_user_group_to_perm:
305 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
306 p.permission.permission_name})
307
308 data = render(
309 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
310 self._get_template_context(c), self.request)
311 html = formencode.htmlfill.render(
312 data,
313 defaults=defaults,
314 encoding="UTF-8",
315 force_defaults=False
316 )
317 return Response(html)
318
319 @LoginRequired()
320 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
321 @CSRFRequired()
322 @view_config(
323 route_name='edit_user_group_perms_update', request_method='POST',
324 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
325 def user_group_update_perms(self):
326 """
327 grant permission for given user group
328 """
329 _ = self.request.translate
330
331 user_group = self.db_user_group
332 user_group_id = user_group.users_group_id
333 c = self.load_default_context()
334 c.user_group = user_group
335 form = UserGroupPermsForm()().to_python(self.request.POST)
336
337 if not self._rhodecode_user.is_admin:
338 if self._revoke_perms_on_yourself(form):
339 msg = _('Cannot change permission for yourself as admin')
340 h.flash(msg, category='warning')
341 raise HTTPFound(
342 h.route_path('edit_user_group_perms',
343 user_group_id=user_group_id))
344
345 try:
346 changes = UserGroupModel().update_permissions(
347 user_group_id,
348 form['perm_additions'], form['perm_updates'],
349 form['perm_deletions'])
350
351 except RepoGroupAssignmentError:
352 h.flash(_('Target group cannot be the same'), category='error')
353 raise HTTPFound(
354 h.route_path('edit_user_group_perms',
355 user_group_id=user_group_id))
356
357 action_data = {
358 'added': changes['added'],
359 'updated': changes['updated'],
360 'deleted': changes['deleted'],
361 }
362 audit_logger.store_web(
363 'user_group.edit.permissions', action_data=action_data,
364 user=self._rhodecode_user)
365
366 Session().commit()
367 h.flash(_('User Group permissions updated'), category='success')
368 raise HTTPFound(
369 h.route_path('edit_user_group_perms', user_group_id=user_group_id))
370
371 @LoginRequired()
372 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
373 @view_config(
374 route_name='edit_user_group_global_perms', request_method='GET',
375 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
376 def user_group_global_perms_edit(self):
377 user_group = self.db_user_group
378 c = self.load_default_context()
379 c.user_group = user_group
380 c.active = 'global_perms'
381
382 c.default_user = User.get_default_user()
383 defaults = c.user_group.get_dict()
384 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
385 defaults.update(c.user_group.get_default_perms())
386
387 data = render(
388 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
389 self._get_template_context(c), self.request)
390 html = formencode.htmlfill.render(
391 data,
392 defaults=defaults,
393 encoding="UTF-8",
394 force_defaults=False
395 )
396 return Response(html)
397
398 @LoginRequired()
399 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
400 @CSRFRequired()
401 @view_config(
402 route_name='edit_user_group_global_perms_update', request_method='POST',
403 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
404 def user_group_global_perms_update(self):
405 _ = self.request.translate
406 user_group = self.db_user_group
407 user_group_id = self.db_user_group.users_group_id
408
409 c = self.load_default_context()
410 c.user_group = user_group
411 c.active = 'global_perms'
412
413 try:
414 # first stage that verifies the checkbox
415 _form = UserIndividualPermissionsForm()
416 form_result = _form.to_python(dict(self.request.POST))
417 inherit_perms = form_result['inherit_default_permissions']
418 user_group.inherit_default_permissions = inherit_perms
419 Session().add(user_group)
420
421 if not inherit_perms:
422 # only update the individual ones if we un check the flag
423 _form = UserPermissionsForm(
424 [x[0] for x in c.repo_create_choices],
425 [x[0] for x in c.repo_create_on_write_choices],
426 [x[0] for x in c.repo_group_create_choices],
427 [x[0] for x in c.user_group_create_choices],
428 [x[0] for x in c.fork_choices],
429 [x[0] for x in c.inherit_default_permission_choices])()
430
431 form_result = _form.to_python(dict(self.request.POST))
432 form_result.update(
433 {'perm_user_group_id': user_group.users_group_id})
434
435 PermissionModel().update_user_group_permissions(form_result)
436
437 Session().commit()
438 h.flash(_('User Group global permissions updated successfully'),
439 category='success')
440
441 except formencode.Invalid as errors:
442 defaults = errors.value
443
444 data = render(
445 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
446 self._get_template_context(c), self.request)
447 html = formencode.htmlfill.render(
448 data,
449 defaults=defaults,
450 errors=errors.error_dict or {},
451 prefix_error=False,
452 encoding="UTF-8",
453 force_defaults=False
454 )
455 return Response(html)
456 except Exception:
457 log.exception("Exception during permissions saving")
458 h.flash(_('An error occurred during permissions saving'),
459 category='error')
460
461 raise HTTPFound(
462 h.route_path('edit_user_group_global_perms',
463 user_group_id=user_group_id))
464
465 @LoginRequired()
466 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
467 @view_config(
468 route_name='edit_user_group_advanced', request_method='GET',
469 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
470 def user_group_edit_advanced(self):
471 user_group = self.db_user_group
472
473 c = self.load_default_context()
474 c.user_group = user_group
475 c.active = 'advanced'
476 c.group_members_obj = sorted(
477 (x.user for x in c.user_group.members),
478 key=lambda u: u.username.lower())
479
480 c.group_to_repos = sorted(
481 (x.repository for x in c.user_group.users_group_repo_to_perm),
482 key=lambda u: u.repo_name.lower())
483
484 c.group_to_repo_groups = sorted(
485 (x.group for x in c.user_group.users_group_repo_group_to_perm),
486 key=lambda u: u.group_name.lower())
487
488 c.group_to_review_rules = sorted(
489 (x.users_group for x in c.user_group.user_group_review_rules),
490 key=lambda u: u.users_group_name.lower())
491
492 return self._get_template_context(c)
493
494 @LoginRequired()
495 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
496 @CSRFRequired()
497 @view_config(
498 route_name='edit_user_group_advanced_sync', request_method='POST',
499 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
500 def user_group_edit_advanced_set_synchronization(self):
501 _ = self.request.translate
502 user_group = self.db_user_group
503 user_group_id = user_group.users_group_id
504
505 existing = user_group.group_data.get('extern_type')
506
507 if existing:
508 new_state = user_group.group_data
509 new_state['extern_type'] = None
510 else:
511 new_state = user_group.group_data
512 new_state['extern_type'] = 'manual'
513 new_state['extern_type_set_by'] = self._rhodecode_user.username
514
515 try:
516 user_group.group_data = new_state
517 Session().add(user_group)
518 Session().commit()
519
520 h.flash(_('User Group synchronization updated successfully'),
521 category='success')
522 except Exception:
523 log.exception("Exception during sync settings saving")
524 h.flash(_('An error occurred during synchronization update'),
525 category='error')
526
527 raise HTTPFound(
528 h.route_path('edit_user_group_advanced',
529 user_group_id=user_group_id))
@@ -1,471 +1,508 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model.db import User
33 from rhodecode.model.db import User
33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
34
35
35 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
36
37
37
38
38 ADMIN_PREFIX = '/_admin'
39 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
40 STATIC_FILE_PREFIX = '/_static'
40
41
41 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
42 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
43 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
44 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
46 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
47 # file path eats up everything at the end
48 # file path eats up everything at the end
48 'f_path': r'.*',
49 'f_path': r'.*',
49 # reference types
50 # reference types
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 }
53 }
53
54
54
55
55 def add_route_with_slash(config,name, pattern, **kw):
56 def add_route_with_slash(config,name, pattern, **kw):
56 config.add_route(name, pattern, **kw)
57 config.add_route(name, pattern, **kw)
57 if not pattern.endswith('/'):
58 if not pattern.endswith('/'):
58 config.add_route(name + '_slash', pattern + '/', **kw)
59 config.add_route(name + '_slash', pattern + '/', **kw)
59
60
60
61
61 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
62 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
62 """
63 """
63 Adds regex requirements to pyramid routes using a mapping dict
64 Adds regex requirements to pyramid routes using a mapping dict
64 e.g::
65 e.g::
65 add_route_requirements('{repo_name}/settings')
66 add_route_requirements('{repo_name}/settings')
66 """
67 """
67 for key, regex in requirements.items():
68 for key, regex in requirements.items():
68 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
69 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
69 return route_path
70 return route_path
70
71
71
72
72 def get_format_ref_id(repo):
73 def get_format_ref_id(repo):
73 """Returns a `repo` specific reference formatter function"""
74 """Returns a `repo` specific reference formatter function"""
74 if h.is_svn(repo):
75 if h.is_svn(repo):
75 return _format_ref_id_svn
76 return _format_ref_id_svn
76 else:
77 else:
77 return _format_ref_id
78 return _format_ref_id
78
79
79
80
80 def _format_ref_id(name, raw_id):
81 def _format_ref_id(name, raw_id):
81 """Default formatting of a given reference `name`"""
82 """Default formatting of a given reference `name`"""
82 return name
83 return name
83
84
84
85
85 def _format_ref_id_svn(name, raw_id):
86 def _format_ref_id_svn(name, raw_id):
86 """Special way of formatting a reference for Subversion including path"""
87 """Special way of formatting a reference for Subversion including path"""
87 return '%s@%s' % (name, raw_id)
88 return '%s@%s' % (name, raw_id)
88
89
89
90
90 class TemplateArgs(StrictAttributeDict):
91 class TemplateArgs(StrictAttributeDict):
91 pass
92 pass
92
93
93
94
94 class BaseAppView(object):
95 class BaseAppView(object):
95
96
96 def __init__(self, context, request):
97 def __init__(self, context, request):
97 self.request = request
98 self.request = request
98 self.context = context
99 self.context = context
99 self.session = request.session
100 self.session = request.session
100 self._rhodecode_user = request.user # auth user
101 self._rhodecode_user = request.user # auth user
101 self._rhodecode_db_user = self._rhodecode_user.get_instance()
102 self._rhodecode_db_user = self._rhodecode_user.get_instance()
102 self._maybe_needs_password_change(
103 self._maybe_needs_password_change(
103 request.matched_route.name, self._rhodecode_db_user)
104 request.matched_route.name, self._rhodecode_db_user)
104
105
105 def _maybe_needs_password_change(self, view_name, user_obj):
106 def _maybe_needs_password_change(self, view_name, user_obj):
106 log.debug('Checking if user %s needs password change on view %s',
107 log.debug('Checking if user %s needs password change on view %s',
107 user_obj, view_name)
108 user_obj, view_name)
108 skip_user_views = [
109 skip_user_views = [
109 'logout', 'login',
110 'logout', 'login',
110 'my_account_password', 'my_account_password_update'
111 'my_account_password', 'my_account_password_update'
111 ]
112 ]
112
113
113 if not user_obj:
114 if not user_obj:
114 return
115 return
115
116
116 if user_obj.username == User.DEFAULT_USER:
117 if user_obj.username == User.DEFAULT_USER:
117 return
118 return
118
119
119 now = time.time()
120 now = time.time()
120 should_change = user_obj.user_data.get('force_password_change')
121 should_change = user_obj.user_data.get('force_password_change')
121 change_after = safe_int(should_change) or 0
122 change_after = safe_int(should_change) or 0
122 if should_change and now > change_after:
123 if should_change and now > change_after:
123 log.debug('User %s requires password change', user_obj)
124 log.debug('User %s requires password change', user_obj)
124 h.flash('You are required to change your password', 'warning',
125 h.flash('You are required to change your password', 'warning',
125 ignore_duplicate=True)
126 ignore_duplicate=True)
126
127
127 if view_name not in skip_user_views:
128 if view_name not in skip_user_views:
128 raise HTTPFound(
129 raise HTTPFound(
129 self.request.route_path('my_account_password'))
130 self.request.route_path('my_account_password'))
130
131
131 def _log_creation_exception(self, e, repo_name):
132 def _log_creation_exception(self, e, repo_name):
132 _ = self.request.translate
133 _ = self.request.translate
133 reason = None
134 reason = None
134 if len(e.args) == 2:
135 if len(e.args) == 2:
135 reason = e.args[1]
136 reason = e.args[1]
136
137
137 if reason == 'INVALID_CERTIFICATE':
138 if reason == 'INVALID_CERTIFICATE':
138 log.exception(
139 log.exception(
139 'Exception creating a repository: invalid certificate')
140 'Exception creating a repository: invalid certificate')
140 msg = (_('Error creating repository %s: invalid certificate')
141 msg = (_('Error creating repository %s: invalid certificate')
141 % repo_name)
142 % repo_name)
142 else:
143 else:
143 log.exception("Exception creating a repository")
144 log.exception("Exception creating a repository")
144 msg = (_('Error creating repository %s')
145 msg = (_('Error creating repository %s')
145 % repo_name)
146 % repo_name)
146 return msg
147 return msg
147
148
148 def _get_local_tmpl_context(self, include_app_defaults=False):
149 def _get_local_tmpl_context(self, include_app_defaults=False):
149 c = TemplateArgs()
150 c = TemplateArgs()
150 c.auth_user = self.request.user
151 c.auth_user = self.request.user
151 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
152 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
152 c.rhodecode_user = self.request.user
153 c.rhodecode_user = self.request.user
153
154
154 if include_app_defaults:
155 if include_app_defaults:
155 # NOTE(marcink): after full pyramid migration include_app_defaults
156 # NOTE(marcink): after full pyramid migration include_app_defaults
156 # should be turned on by default
157 # should be turned on by default
157 from rhodecode.lib.base import attach_context_attributes
158 from rhodecode.lib.base import attach_context_attributes
158 attach_context_attributes(c, self.request, self.request.user.user_id)
159 attach_context_attributes(c, self.request, self.request.user.user_id)
159
160
160 return c
161 return c
161
162
162 def _register_global_c(self, tmpl_args):
163 def _register_global_c(self, tmpl_args):
163 """
164 """
164 Registers attributes to pylons global `c`
165 Registers attributes to pylons global `c`
165 """
166 """
166
167
167 # TODO(marcink): remove once pyramid migration is finished
168 # TODO(marcink): remove once pyramid migration is finished
168 from pylons import tmpl_context as c
169 from pylons import tmpl_context as c
169 try:
170 try:
170 for k, v in tmpl_args.items():
171 for k, v in tmpl_args.items():
171 setattr(c, k, v)
172 setattr(c, k, v)
172 except TypeError:
173 except TypeError:
173 log.exception('Failed to register pylons C')
174 log.exception('Failed to register pylons C')
174 pass
175 pass
175
176
176 def _get_template_context(self, tmpl_args):
177 def _get_template_context(self, tmpl_args):
177 self._register_global_c(tmpl_args)
178 self._register_global_c(tmpl_args)
178
179
179 local_tmpl_args = {
180 local_tmpl_args = {
180 'defaults': {},
181 'defaults': {},
181 'errors': {},
182 'errors': {},
182 # register a fake 'c' to be used in templates instead of global
183 # register a fake 'c' to be used in templates instead of global
183 # pylons c, after migration to pyramid we should rename it to 'c'
184 # pylons c, after migration to pyramid we should rename it to 'c'
184 # make sure we replace usage of _c in templates too
185 # make sure we replace usage of _c in templates too
185 '_c': tmpl_args
186 '_c': tmpl_args
186 }
187 }
187 local_tmpl_args.update(tmpl_args)
188 local_tmpl_args.update(tmpl_args)
188 return local_tmpl_args
189 return local_tmpl_args
189
190
190 def load_default_context(self):
191 def load_default_context(self):
191 """
192 """
192 example:
193 example:
193
194
194 def load_default_context(self):
195 def load_default_context(self):
195 c = self._get_local_tmpl_context()
196 c = self._get_local_tmpl_context()
196 c.custom_var = 'foobar'
197 c.custom_var = 'foobar'
197 self._register_global_c(c)
198 self._register_global_c(c)
198 return c
199 return c
199 """
200 """
200 raise NotImplementedError('Needs implementation in view class')
201 raise NotImplementedError('Needs implementation in view class')
201
202
202
203
203 class RepoAppView(BaseAppView):
204 class RepoAppView(BaseAppView):
204
205
205 def __init__(self, context, request):
206 def __init__(self, context, request):
206 super(RepoAppView, self).__init__(context, request)
207 super(RepoAppView, self).__init__(context, request)
207 self.db_repo = request.db_repo
208 self.db_repo = request.db_repo
208 self.db_repo_name = self.db_repo.repo_name
209 self.db_repo_name = self.db_repo.repo_name
209 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
210 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
210
211
211 def _handle_missing_requirements(self, error):
212 def _handle_missing_requirements(self, error):
212 log.error(
213 log.error(
213 'Requirements are missing for repository %s: %s',
214 'Requirements are missing for repository %s: %s',
214 self.db_repo_name, error.message)
215 self.db_repo_name, error.message)
215
216
216 def _get_local_tmpl_context(self, include_app_defaults=False):
217 def _get_local_tmpl_context(self, include_app_defaults=False):
217 _ = self.request.translate
218 _ = self.request.translate
218 c = super(RepoAppView, self)._get_local_tmpl_context(
219 c = super(RepoAppView, self)._get_local_tmpl_context(
219 include_app_defaults=include_app_defaults)
220 include_app_defaults=include_app_defaults)
220
221
221 # register common vars for this type of view
222 # register common vars for this type of view
222 c.rhodecode_db_repo = self.db_repo
223 c.rhodecode_db_repo = self.db_repo
223 c.repo_name = self.db_repo_name
224 c.repo_name = self.db_repo_name
224 c.repository_pull_requests = self.db_repo_pull_requests
225 c.repository_pull_requests = self.db_repo_pull_requests
225
226
226 c.repository_requirements_missing = False
227 c.repository_requirements_missing = False
227 try:
228 try:
228 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
229 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
229 except RepositoryRequirementError as e:
230 except RepositoryRequirementError as e:
230 c.repository_requirements_missing = True
231 c.repository_requirements_missing = True
231 self._handle_missing_requirements(e)
232 self._handle_missing_requirements(e)
232 self.rhodecode_vcs_repo = None
233 self.rhodecode_vcs_repo = None
233
234
234 if (not c.repository_requirements_missing
235 if (not c.repository_requirements_missing
235 and self.rhodecode_vcs_repo is None):
236 and self.rhodecode_vcs_repo is None):
236 # unable to fetch this repo as vcs instance, report back to user
237 # unable to fetch this repo as vcs instance, report back to user
237 h.flash(_(
238 h.flash(_(
238 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
239 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
239 "Please check if it exist, or is not damaged.") %
240 "Please check if it exist, or is not damaged.") %
240 {'repo_name': c.repo_name},
241 {'repo_name': c.repo_name},
241 category='error', ignore_duplicate=True)
242 category='error', ignore_duplicate=True)
242 raise HTTPFound(h.route_path('home'))
243 raise HTTPFound(h.route_path('home'))
243
244
244 return c
245 return c
245
246
246 def _get_f_path(self, matchdict, default=None):
247 def _get_f_path(self, matchdict, default=None):
247 f_path = matchdict.get('f_path')
248 f_path = matchdict.get('f_path')
248 if f_path:
249 if f_path:
249 # fix for multiple initial slashes that causes errors for GIT
250 # fix for multiple initial slashes that causes errors for GIT
250 return f_path.lstrip('/')
251 return f_path.lstrip('/')
251
252
252 return default
253 return default
253
254
254
255
255 class RepoGroupAppView(BaseAppView):
256 class RepoGroupAppView(BaseAppView):
256 def __init__(self, context, request):
257 def __init__(self, context, request):
257 super(RepoGroupAppView, self).__init__(context, request)
258 super(RepoGroupAppView, self).__init__(context, request)
258 self.db_repo_group = request.db_repo_group
259 self.db_repo_group = request.db_repo_group
259 self.db_repo_group_name = self.db_repo_group.group_name
260 self.db_repo_group_name = self.db_repo_group.group_name
260
261
261
262
263 class UserGroupAppView(BaseAppView):
264 def __init__(self, context, request):
265 super(UserGroupAppView, self).__init__(context, request)
266 self.db_user_group = request.db_user_group
267 self.db_user_group_name = self.db_user_group.users_group_name
268
269
262 class DataGridAppView(object):
270 class DataGridAppView(object):
263 """
271 """
264 Common class to have re-usable grid rendering components
272 Common class to have re-usable grid rendering components
265 """
273 """
266
274
267 def _extract_ordering(self, request, column_map=None):
275 def _extract_ordering(self, request, column_map=None):
268 column_map = column_map or {}
276 column_map = column_map or {}
269 column_index = safe_int(request.GET.get('order[0][column]'))
277 column_index = safe_int(request.GET.get('order[0][column]'))
270 order_dir = request.GET.get(
278 order_dir = request.GET.get(
271 'order[0][dir]', 'desc')
279 'order[0][dir]', 'desc')
272 order_by = request.GET.get(
280 order_by = request.GET.get(
273 'columns[%s][data][sort]' % column_index, 'name_raw')
281 'columns[%s][data][sort]' % column_index, 'name_raw')
274
282
275 # translate datatable to DB columns
283 # translate datatable to DB columns
276 order_by = column_map.get(order_by) or order_by
284 order_by = column_map.get(order_by) or order_by
277
285
278 search_q = request.GET.get('search[value]')
286 search_q = request.GET.get('search[value]')
279 return search_q, order_by, order_dir
287 return search_q, order_by, order_dir
280
288
281 def _extract_chunk(self, request):
289 def _extract_chunk(self, request):
282 start = safe_int(request.GET.get('start'), 0)
290 start = safe_int(request.GET.get('start'), 0)
283 length = safe_int(request.GET.get('length'), 25)
291 length = safe_int(request.GET.get('length'), 25)
284 draw = safe_int(request.GET.get('draw'))
292 draw = safe_int(request.GET.get('draw'))
285 return draw, start, length
293 return draw, start, length
286
294
287 def _get_order_col(self, order_by, model):
295 def _get_order_col(self, order_by, model):
288 if isinstance(order_by, basestring):
296 if isinstance(order_by, basestring):
289 try:
297 try:
290 return operator.attrgetter(order_by)(model)
298 return operator.attrgetter(order_by)(model)
291 except AttributeError:
299 except AttributeError:
292 return None
300 return None
293 else:
301 else:
294 return order_by
302 return order_by
295
303
296
304
297 class BaseReferencesView(RepoAppView):
305 class BaseReferencesView(RepoAppView):
298 """
306 """
299 Base for reference view for branches, tags and bookmarks.
307 Base for reference view for branches, tags and bookmarks.
300 """
308 """
301 def load_default_context(self):
309 def load_default_context(self):
302 c = self._get_local_tmpl_context()
310 c = self._get_local_tmpl_context()
303
311
304 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
312 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
305 c.repo_info = self.db_repo
313 c.repo_info = self.db_repo
306
314
307 self._register_global_c(c)
315 self._register_global_c(c)
308 return c
316 return c
309
317
310 def load_refs_context(self, ref_items, partials_template):
318 def load_refs_context(self, ref_items, partials_template):
311 _render = self.request.get_partial_renderer(partials_template)
319 _render = self.request.get_partial_renderer(partials_template)
312 pre_load = ["author", "date", "message"]
320 pre_load = ["author", "date", "message"]
313
321
314 is_svn = h.is_svn(self.rhodecode_vcs_repo)
322 is_svn = h.is_svn(self.rhodecode_vcs_repo)
315 is_hg = h.is_hg(self.rhodecode_vcs_repo)
323 is_hg = h.is_hg(self.rhodecode_vcs_repo)
316
324
317 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
325 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
318
326
319 closed_refs = {}
327 closed_refs = {}
320 if is_hg:
328 if is_hg:
321 closed_refs = self.rhodecode_vcs_repo.branches_closed
329 closed_refs = self.rhodecode_vcs_repo.branches_closed
322
330
323 data = []
331 data = []
324 for ref_name, commit_id in ref_items:
332 for ref_name, commit_id in ref_items:
325 commit = self.rhodecode_vcs_repo.get_commit(
333 commit = self.rhodecode_vcs_repo.get_commit(
326 commit_id=commit_id, pre_load=pre_load)
334 commit_id=commit_id, pre_load=pre_load)
327 closed = ref_name in closed_refs
335 closed = ref_name in closed_refs
328
336
329 # TODO: johbo: Unify generation of reference links
337 # TODO: johbo: Unify generation of reference links
330 use_commit_id = '/' in ref_name or is_svn
338 use_commit_id = '/' in ref_name or is_svn
331
339
332 if use_commit_id:
340 if use_commit_id:
333 files_url = h.route_path(
341 files_url = h.route_path(
334 'repo_files',
342 'repo_files',
335 repo_name=self.db_repo_name,
343 repo_name=self.db_repo_name,
336 f_path=ref_name if is_svn else '',
344 f_path=ref_name if is_svn else '',
337 commit_id=commit_id)
345 commit_id=commit_id)
338
346
339 else:
347 else:
340 files_url = h.route_path(
348 files_url = h.route_path(
341 'repo_files',
349 'repo_files',
342 repo_name=self.db_repo_name,
350 repo_name=self.db_repo_name,
343 f_path=ref_name if is_svn else '',
351 f_path=ref_name if is_svn else '',
344 commit_id=ref_name,
352 commit_id=ref_name,
345 _query=dict(at=ref_name))
353 _query=dict(at=ref_name))
346
354
347 data.append({
355 data.append({
348 "name": _render('name', ref_name, files_url, closed),
356 "name": _render('name', ref_name, files_url, closed),
349 "name_raw": ref_name,
357 "name_raw": ref_name,
350 "date": _render('date', commit.date),
358 "date": _render('date', commit.date),
351 "date_raw": datetime_to_time(commit.date),
359 "date_raw": datetime_to_time(commit.date),
352 "author": _render('author', commit.author),
360 "author": _render('author', commit.author),
353 "commit": _render(
361 "commit": _render(
354 'commit', commit.message, commit.raw_id, commit.idx),
362 'commit', commit.message, commit.raw_id, commit.idx),
355 "commit_raw": commit.idx,
363 "commit_raw": commit.idx,
356 "compare": _render(
364 "compare": _render(
357 'compare', format_ref_id(ref_name, commit.raw_id)),
365 'compare', format_ref_id(ref_name, commit.raw_id)),
358 })
366 })
359
367
360 return data
368 return data
361
369
362
370
363 class RepoRoutePredicate(object):
371 class RepoRoutePredicate(object):
364 def __init__(self, val, config):
372 def __init__(self, val, config):
365 self.val = val
373 self.val = val
366
374
367 def text(self):
375 def text(self):
368 return 'repo_route = %s' % self.val
376 return 'repo_route = %s' % self.val
369
377
370 phash = text
378 phash = text
371
379
372 def __call__(self, info, request):
380 def __call__(self, info, request):
373
381
374 if hasattr(request, 'vcs_call'):
382 if hasattr(request, 'vcs_call'):
375 # skip vcs calls
383 # skip vcs calls
376 return
384 return
377
385
378 repo_name = info['match']['repo_name']
386 repo_name = info['match']['repo_name']
379 repo_model = repo.RepoModel()
387 repo_model = repo.RepoModel()
380 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
388 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
381
389
382 def redirect_if_creating(db_repo):
390 def redirect_if_creating(db_repo):
383 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
391 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
384 raise HTTPFound(
392 raise HTTPFound(
385 request.route_path('repo_creating',
393 request.route_path('repo_creating',
386 repo_name=db_repo.repo_name))
394 repo_name=db_repo.repo_name))
387
395
388 if by_name_match:
396 if by_name_match:
389 # register this as request object we can re-use later
397 # register this as request object we can re-use later
390 request.db_repo = by_name_match
398 request.db_repo = by_name_match
391 redirect_if_creating(by_name_match)
399 redirect_if_creating(by_name_match)
392 return True
400 return True
393
401
394 by_id_match = repo_model.get_repo_by_id(repo_name)
402 by_id_match = repo_model.get_repo_by_id(repo_name)
395 if by_id_match:
403 if by_id_match:
396 request.db_repo = by_id_match
404 request.db_repo = by_id_match
397 redirect_if_creating(by_id_match)
405 redirect_if_creating(by_id_match)
398 return True
406 return True
399
407
400 return False
408 return False
401
409
402
410
403 class RepoTypeRoutePredicate(object):
411 class RepoTypeRoutePredicate(object):
404 def __init__(self, val, config):
412 def __init__(self, val, config):
405 self.val = val or ['hg', 'git', 'svn']
413 self.val = val or ['hg', 'git', 'svn']
406
414
407 def text(self):
415 def text(self):
408 return 'repo_accepted_type = %s' % self.val
416 return 'repo_accepted_type = %s' % self.val
409
417
410 phash = text
418 phash = text
411
419
412 def __call__(self, info, request):
420 def __call__(self, info, request):
413 if hasattr(request, 'vcs_call'):
421 if hasattr(request, 'vcs_call'):
414 # skip vcs calls
422 # skip vcs calls
415 return
423 return
416
424
417 rhodecode_db_repo = request.db_repo
425 rhodecode_db_repo = request.db_repo
418
426
419 log.debug(
427 log.debug(
420 '%s checking repo type for %s in %s',
428 '%s checking repo type for %s in %s',
421 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
429 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
422
430
423 if rhodecode_db_repo.repo_type in self.val:
431 if rhodecode_db_repo.repo_type in self.val:
424 return True
432 return True
425 else:
433 else:
426 log.warning('Current view is not supported for repo type:%s',
434 log.warning('Current view is not supported for repo type:%s',
427 rhodecode_db_repo.repo_type)
435 rhodecode_db_repo.repo_type)
428 #
436 #
429 # h.flash(h.literal(
437 # h.flash(h.literal(
430 # _('Action not supported for %s.' % rhodecode_repo.alias)),
438 # _('Action not supported for %s.' % rhodecode_repo.alias)),
431 # category='warning')
439 # category='warning')
432 # return redirect(
440 # return redirect(
433 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
441 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
434
442
435 return False
443 return False
436
444
437
445
438 class RepoGroupRoutePredicate(object):
446 class RepoGroupRoutePredicate(object):
439 def __init__(self, val, config):
447 def __init__(self, val, config):
440 self.val = val
448 self.val = val
441
449
442 def text(self):
450 def text(self):
443 return 'repo_group_route = %s' % self.val
451 return 'repo_group_route = %s' % self.val
444
452
445 phash = text
453 phash = text
446
454
447 def __call__(self, info, request):
455 def __call__(self, info, request):
448 if hasattr(request, 'vcs_call'):
456 if hasattr(request, 'vcs_call'):
449 # skip vcs calls
457 # skip vcs calls
450 return
458 return
451
459
452 repo_group_name = info['match']['repo_group_name']
460 repo_group_name = info['match']['repo_group_name']
453 repo_group_model = repo_group.RepoGroupModel()
461 repo_group_model = repo_group.RepoGroupModel()
454 by_name_match = repo_group_model.get_by_group_name(
462 by_name_match = repo_group_model.get_by_group_name(
455 repo_group_name, cache=True)
463 repo_group_name, cache=True)
456
464
457 if by_name_match:
465 if by_name_match:
458 # register this as request object we can re-use later
466 # register this as request object we can re-use later
459 request.db_repo_group = by_name_match
467 request.db_repo_group = by_name_match
460 return True
468 return True
461
469
462 return False
470 return False
463
471
464
472
473 class UserGroupRoutePredicate(object):
474 def __init__(self, val, config):
475 self.val = val
476
477 def text(self):
478 return 'user_group_route = %s' % self.val
479
480 phash = text
481
482 def __call__(self, info, request):
483 if hasattr(request, 'vcs_call'):
484 # skip vcs calls
485 return
486
487 user_group_id = info['match']['user_group_id']
488 user_group_model = user_group.UserGroup()
489 by_name_match = user_group_model.get(
490 user_group_id, cache=True)
491
492 if by_name_match:
493 # register this as request object we can re-use later
494 request.db_user_group = by_name_match
495 return True
496
497 return False
498
499
465 def includeme(config):
500 def includeme(config):
466 config.add_route_predicate(
501 config.add_route_predicate(
467 'repo_route', RepoRoutePredicate)
502 'repo_route', RepoRoutePredicate)
468 config.add_route_predicate(
503 config.add_route_predicate(
469 'repo_accepted_types', RepoTypeRoutePredicate)
504 'repo_accepted_types', RepoTypeRoutePredicate)
470 config.add_route_predicate(
505 config.add_route_predicate(
471 'repo_group_route', RepoGroupRoutePredicate)
506 'repo_group_route', RepoGroupRoutePredicate)
507 config.add_route_predicate(
508 'user_group_route', UserGroupRoutePredicate)
@@ -1,245 +1,241 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:\d+}')
38 pattern='/pull_requests/{pull_request_id:\d+}')
39 config.add_route(
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:\d+}')
41 pattern='/pull-requests/{pull_request_id:\d+}')
42 config.add_route(
42 config.add_route(
43 name='pull_requests_global',
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:\d+}')
44 pattern='/pull-request/{pull_request_id:\d+}')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_open_source',
47 name='admin_settings_open_source',
48 pattern='/settings/open_source')
48 pattern='/settings/open_source')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
51 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
52
52
53 config.add_route(
53 config.add_route(
54 name='admin_settings_system',
54 name='admin_settings_system',
55 pattern='/settings/system')
55 pattern='/settings/system')
56 config.add_route(
56 config.add_route(
57 name='admin_settings_system_update',
57 name='admin_settings_system_update',
58 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
59
59
60 config.add_route(
60 config.add_route(
61 name='admin_settings_sessions',
61 name='admin_settings_sessions',
62 pattern='/settings/sessions')
62 pattern='/settings/sessions')
63 config.add_route(
63 config.add_route(
64 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
65 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
66
66
67 config.add_route(
67 config.add_route(
68 name='admin_settings_process_management',
68 name='admin_settings_process_management',
69 pattern='/settings/process_management')
69 pattern='/settings/process_management')
70 config.add_route(
70 config.add_route(
71 name='admin_settings_process_management_signal',
71 name='admin_settings_process_management_signal',
72 pattern='/settings/process_management/signal')
72 pattern='/settings/process_management/signal')
73
73
74 # global permissions
74 # global permissions
75
75
76 config.add_route(
76 config.add_route(
77 name='admin_permissions_application',
77 name='admin_permissions_application',
78 pattern='/permissions/application')
78 pattern='/permissions/application')
79 config.add_route(
79 config.add_route(
80 name='admin_permissions_application_update',
80 name='admin_permissions_application_update',
81 pattern='/permissions/application/update')
81 pattern='/permissions/application/update')
82
82
83 config.add_route(
83 config.add_route(
84 name='admin_permissions_global',
84 name='admin_permissions_global',
85 pattern='/permissions/global')
85 pattern='/permissions/global')
86 config.add_route(
86 config.add_route(
87 name='admin_permissions_global_update',
87 name='admin_permissions_global_update',
88 pattern='/permissions/global/update')
88 pattern='/permissions/global/update')
89
89
90 config.add_route(
90 config.add_route(
91 name='admin_permissions_object',
91 name='admin_permissions_object',
92 pattern='/permissions/object')
92 pattern='/permissions/object')
93 config.add_route(
93 config.add_route(
94 name='admin_permissions_object_update',
94 name='admin_permissions_object_update',
95 pattern='/permissions/object/update')
95 pattern='/permissions/object/update')
96
96
97 config.add_route(
97 config.add_route(
98 name='admin_permissions_ips',
98 name='admin_permissions_ips',
99 pattern='/permissions/ips')
99 pattern='/permissions/ips')
100
100
101 config.add_route(
101 config.add_route(
102 name='admin_permissions_overview',
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
103 pattern='/permissions/overview')
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_permissions_auth_token_access',
106 name='admin_permissions_auth_token_access',
107 pattern='/permissions/auth_token_access')
107 pattern='/permissions/auth_token_access')
108
108
109 config.add_route(
109 config.add_route(
110 name='admin_permissions_ssh_keys',
110 name='admin_permissions_ssh_keys',
111 pattern='/permissions/ssh_keys')
111 pattern='/permissions/ssh_keys')
112 config.add_route(
112 config.add_route(
113 name='admin_permissions_ssh_keys_data',
113 name='admin_permissions_ssh_keys_data',
114 pattern='/permissions/ssh_keys/data')
114 pattern='/permissions/ssh_keys/data')
115 config.add_route(
115 config.add_route(
116 name='admin_permissions_ssh_keys_update',
116 name='admin_permissions_ssh_keys_update',
117 pattern='/permissions/ssh_keys/update')
117 pattern='/permissions/ssh_keys/update')
118
118
119 # users admin
119 # users admin
120 config.add_route(
120 config.add_route(
121 name='users',
121 name='users',
122 pattern='/users')
122 pattern='/users')
123
123
124 config.add_route(
124 config.add_route(
125 name='users_data',
125 name='users_data',
126 pattern='/users_data')
126 pattern='/users_data')
127
127
128 # user auth tokens
128 # user auth tokens
129 config.add_route(
129 config.add_route(
130 name='edit_user_auth_tokens',
130 name='edit_user_auth_tokens',
131 pattern='/users/{user_id:\d+}/edit/auth_tokens')
131 pattern='/users/{user_id:\d+}/edit/auth_tokens')
132 config.add_route(
132 config.add_route(
133 name='edit_user_auth_tokens_add',
133 name='edit_user_auth_tokens_add',
134 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
134 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
135 config.add_route(
135 config.add_route(
136 name='edit_user_auth_tokens_delete',
136 name='edit_user_auth_tokens_delete',
137 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
137 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
138
138
139 # user ssh keys
139 # user ssh keys
140 config.add_route(
140 config.add_route(
141 name='edit_user_ssh_keys',
141 name='edit_user_ssh_keys',
142 pattern='/users/{user_id:\d+}/edit/ssh_keys')
142 pattern='/users/{user_id:\d+}/edit/ssh_keys')
143 config.add_route(
143 config.add_route(
144 name='edit_user_ssh_keys_generate_keypair',
144 name='edit_user_ssh_keys_generate_keypair',
145 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
145 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
146 config.add_route(
146 config.add_route(
147 name='edit_user_ssh_keys_add',
147 name='edit_user_ssh_keys_add',
148 pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
148 pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
149 config.add_route(
149 config.add_route(
150 name='edit_user_ssh_keys_delete',
150 name='edit_user_ssh_keys_delete',
151 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
151 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
152
152
153 # user emails
153 # user emails
154 config.add_route(
154 config.add_route(
155 name='edit_user_emails',
155 name='edit_user_emails',
156 pattern='/users/{user_id:\d+}/edit/emails')
156 pattern='/users/{user_id:\d+}/edit/emails')
157 config.add_route(
157 config.add_route(
158 name='edit_user_emails_add',
158 name='edit_user_emails_add',
159 pattern='/users/{user_id:\d+}/edit/emails/new')
159 pattern='/users/{user_id:\d+}/edit/emails/new')
160 config.add_route(
160 config.add_route(
161 name='edit_user_emails_delete',
161 name='edit_user_emails_delete',
162 pattern='/users/{user_id:\d+}/edit/emails/delete')
162 pattern='/users/{user_id:\d+}/edit/emails/delete')
163
163
164 # user IPs
164 # user IPs
165 config.add_route(
165 config.add_route(
166 name='edit_user_ips',
166 name='edit_user_ips',
167 pattern='/users/{user_id:\d+}/edit/ips')
167 pattern='/users/{user_id:\d+}/edit/ips')
168 config.add_route(
168 config.add_route(
169 name='edit_user_ips_add',
169 name='edit_user_ips_add',
170 pattern='/users/{user_id:\d+}/edit/ips/new')
170 pattern='/users/{user_id:\d+}/edit/ips/new')
171 config.add_route(
171 config.add_route(
172 name='edit_user_ips_delete',
172 name='edit_user_ips_delete',
173 pattern='/users/{user_id:\d+}/edit/ips/delete')
173 pattern='/users/{user_id:\d+}/edit/ips/delete')
174
174
175 # user perms
175 # user perms
176 config.add_route(
176 config.add_route(
177 name='edit_user_perms_summary',
177 name='edit_user_perms_summary',
178 pattern='/users/{user_id:\d+}/edit/permissions_summary')
178 pattern='/users/{user_id:\d+}/edit/permissions_summary')
179 config.add_route(
179 config.add_route(
180 name='edit_user_perms_summary_json',
180 name='edit_user_perms_summary_json',
181 pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
181 pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
182
182
183 # user groups management
183 # user user groups management
184 config.add_route(
184 config.add_route(
185 name='edit_user_groups_management',
185 name='edit_user_groups_management',
186 pattern='/users/{user_id:\d+}/edit/groups_management')
186 pattern='/users/{user_id:\d+}/edit/groups_management')
187
187
188 config.add_route(
188 config.add_route(
189 name='edit_user_groups_management_updates',
189 name='edit_user_groups_management_updates',
190 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
190 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
191
191
192 # user audit logs
192 # user audit logs
193 config.add_route(
193 config.add_route(
194 name='edit_user_audit_logs',
194 name='edit_user_audit_logs',
195 pattern='/users/{user_id:\d+}/edit/audit')
195 pattern='/users/{user_id:\d+}/edit/audit')
196
196
197 # user groups admin
197 # user-groups admin
198 config.add_route(
198 config.add_route(
199 name='user_groups',
199 name='user_groups',
200 pattern='/user_groups')
200 pattern='/user_groups')
201
201
202 config.add_route(
202 config.add_route(
203 name='user_groups_data',
203 name='user_groups_data',
204 pattern='/user_groups_data')
204 pattern='/user_groups_data')
205
205
206 config.add_route(
206 config.add_route(
207 name='user_group_members_data',
207 name='user_groups_new',
208 pattern='/user_groups/{user_group_id:\d+}/members')
208 pattern='/user_groups/new')
209
209
210 # user groups perms
211 config.add_route(
210 config.add_route(
212 name='edit_user_group_perms_summary',
211 name='user_groups_create',
213 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary')
212 pattern='/user_groups/create')
214 config.add_route(
215 name='edit_user_group_perms_summary_json',
216 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json')
217
213
218 # repos admin
214 # repos admin
219 config.add_route(
215 config.add_route(
220 name='repos',
216 name='repos',
221 pattern='/repos')
217 pattern='/repos')
222
218
223 config.add_route(
219 config.add_route(
224 name='repo_new',
220 name='repo_new',
225 pattern='/repos/new')
221 pattern='/repos/new')
226
222
227 config.add_route(
223 config.add_route(
228 name='repo_create',
224 name='repo_create',
229 pattern='/repos/create')
225 pattern='/repos/create')
230
226
231
227
232 def includeme(config):
228 def includeme(config):
233 settings = config.get_settings()
229 settings = config.get_settings()
234
230
235 # Create admin navigation registry and add it to the pyramid registry.
231 # Create admin navigation registry and add it to the pyramid registry.
236 labs_active = str2bool(settings.get('labs_settings_active', False))
232 labs_active = str2bool(settings.get('labs_settings_active', False))
237 navigation_registry = NavigationRegistry(labs_active=labs_active)
233 navigation_registry = NavigationRegistry(labs_active=labs_active)
238 config.registry.registerUtility(navigation_registry)
234 config.registry.registerUtility(navigation_registry)
239
235
240 # main admin routes
236 # main admin routes
241 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
237 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
242 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
238 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
243
239
244 # Scan module for configuration decorators.
240 # Scan module for configuration decorators.
245 config.scan('.views', ignore='.tests')
241 config.scan('.views', ignore='.tests')
@@ -1,113 +1,170 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserGroup, User
23 from rhodecode.model.db import UserGroup, User
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25
25
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32
32
33 def route_path(name, params=None, **kwargs):
33 def route_path(name, params=None, **kwargs):
34 import urllib
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
35 from rhodecode.apps._base import ADMIN_PREFIX
36
36
37 base_url = {
37 base_url = {
38 'user_groups': ADMIN_PREFIX + '/user_groups',
38 'user_groups': ADMIN_PREFIX + '/user_groups',
39 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
39 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
40 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
40 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
41 'user_groups_new': ADMIN_PREFIX + '/user_groups/new',
42 'user_groups_create': ADMIN_PREFIX + '/user_groups/create',
43 'edit_user_group': ADMIN_PREFIX + '/user_groups/{user_group_id}/edit',
41 }[name].format(**kwargs)
44 }[name].format(**kwargs)
42
45
43 if params:
46 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
48 return base_url
46
49
47
50
48 class TestAdminUserGroupsView(TestController):
51 class TestAdminUserGroupsView(TestController):
49
52
50 def test_show_users(self):
53 def test_show_users(self):
51 self.log_user()
54 self.log_user()
52 self.app.get(route_path('user_groups'))
55 self.app.get(route_path('user_groups'))
53
56
54 def test_show_user_groups_data(self, xhr_header):
57 def test_show_user_groups_data(self, xhr_header):
55 self.log_user()
58 self.log_user()
56 response = self.app.get(route_path(
59 response = self.app.get(route_path(
57 'user_groups_data'), extra_environ=xhr_header)
60 'user_groups_data'), extra_environ=xhr_header)
58
61
59 all_user_groups = UserGroup.query().count()
62 all_user_groups = UserGroup.query().count()
60 assert response.json['recordsTotal'] == all_user_groups
63 assert response.json['recordsTotal'] == all_user_groups
61
64
62 def test_show_user_groups_data_filtered(self, xhr_header):
65 def test_show_user_groups_data_filtered(self, xhr_header):
63 self.log_user()
66 self.log_user()
64 response = self.app.get(route_path(
67 response = self.app.get(route_path(
65 'user_groups_data', params={'search[value]': 'empty_search'}),
68 'user_groups_data', params={'search[value]': 'empty_search'}),
66 extra_environ=xhr_header)
69 extra_environ=xhr_header)
67
70
68 all_user_groups = UserGroup.query().count()
71 all_user_groups = UserGroup.query().count()
69 assert response.json['recordsTotal'] == all_user_groups
72 assert response.json['recordsTotal'] == all_user_groups
70 assert response.json['recordsFiltered'] == 0
73 assert response.json['recordsFiltered'] == 0
71
74
72 def test_usergroup_escape(self, user_util, xhr_header):
75 def test_usergroup_escape(self, user_util, xhr_header):
73 self.log_user()
76 self.log_user()
74
77
75 xss_img = '<img src="/image1" onload="alert(\'Hello, World!\');">'
78 xss_img = '<img src="/image1" onload="alert(\'Hello, World!\');">'
76 user = user_util.create_user()
79 user = user_util.create_user()
77 user.name = xss_img
80 user.name = xss_img
78 user.lastname = xss_img
81 user.lastname = xss_img
79 Session().add(user)
82 Session().add(user)
80 Session().commit()
83 Session().commit()
81
84
82 user_group = user_util.create_user_group()
85 user_group = user_util.create_user_group()
83
86
84 user_group.users_group_name = xss_img
87 user_group.users_group_name = xss_img
85 user_group.user_group_description = '<strong onload="alert();">DESC</strong>'
88 user_group.user_group_description = '<strong onload="alert();">DESC</strong>'
86
89
87 response = self.app.get(
90 response = self.app.get(
88 route_path('user_groups_data'), extra_environ=xhr_header)
91 route_path('user_groups_data'), extra_environ=xhr_header)
89
92
90 response.mustcontain(
93 response.mustcontain(
91 '&lt;strong onload=&#34;alert();&#34;&gt;DESC&lt;/strong&gt;')
94 '&lt;strong onload=&#34;alert();&#34;&gt;DESC&lt;/strong&gt;')
92 response.mustcontain(
95 response.mustcontain(
93 '&lt;img src=&#34;/image1&#34; onload=&#34;'
96 '&lt;img src=&#34;/image1&#34; onload=&#34;'
94 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
97 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
95
98
96 def test_edit_user_group_autocomplete_empty_members(self, xhr_header, user_util):
99 def test_edit_user_group_autocomplete_empty_members(self, xhr_header, user_util):
97 self.log_user()
100 self.log_user()
98 ug = user_util.create_user_group()
101 ug = user_util.create_user_group()
99 response = self.app.get(
102 response = self.app.get(
100 route_path('user_group_members_data', user_group_id=ug.users_group_id),
103 route_path('user_group_members_data', user_group_id=ug.users_group_id),
101 extra_environ=xhr_header)
104 extra_environ=xhr_header)
102
105
103 assert response.json == {'members': []}
106 assert response.json == {'members': []}
104
107
105 def test_edit_user_group_autocomplete_members(self, xhr_header, user_util):
108 def test_edit_user_group_autocomplete_members(self, xhr_header, user_util):
106 self.log_user()
109 self.log_user()
107 members = [u.user_id for u in User.get_all()]
110 members = [u.user_id for u in User.get_all()]
108 ug = user_util.create_user_group(members=members)
111 ug = user_util.create_user_group(members=members)
109 response = self.app.get(
112 response = self.app.get(
110 route_path('user_group_members_data', user_group_id=ug.users_group_id),
113 route_path('user_group_members_data',
114 user_group_id=ug.users_group_id),
111 extra_environ=xhr_header)
115 extra_environ=xhr_header)
112
116
113 assert len(response.json['members']) == len(members)
117 assert len(response.json['members']) == len(members)
118
119 def test_creation_page(self):
120 self.log_user()
121 self.app.get(route_path('user_groups_new'), status=200)
122
123 def test_create(self):
124 from rhodecode.lib import helpers as h
125
126 self.log_user()
127 users_group_name = 'test_user_group'
128 response = self.app.post(route_path('user_groups_create'), {
129 'users_group_name': users_group_name,
130 'user_group_description': 'DESC',
131 'active': True,
132 'csrf_token': self.csrf_token})
133
134 user_group_id = UserGroup.get_by_group_name(
135 users_group_name).users_group_id
136
137 user_group_link = h.link_to(
138 users_group_name,
139 route_path('edit_user_group', user_group_id=user_group_id))
140
141 assert_session_flash(
142 response,
143 'Created user group %s' % user_group_link)
144
145 fixture.destroy_user_group(users_group_name)
146
147 def test_create_with_empty_name(self):
148 self.log_user()
149
150 response = self.app.post(route_path('user_groups_create'), {
151 'users_group_name': '',
152 'user_group_description': 'DESC',
153 'active': True,
154 'csrf_token': self.csrf_token}, status=200)
155
156 response.mustcontain('Please enter a value')
157
158 def test_create_duplicate(self, user_util):
159 self.log_user()
160
161 user_group = user_util.create_user_group()
162 duplicate_name = user_group.users_group_name
163 response = self.app.post(route_path('user_groups_create'), {
164 'users_group_name': duplicate_name,
165 'user_group_description': 'DESC',
166 'active': True,
167 'csrf_token': self.csrf_token}, status=200)
168
169 response.mustcontain(
170 'User group `{}` already exists'.format(user_group.users_group_name))
@@ -1,256 +1,247 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
24 import formencode.htmlfill
25
23 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
27 from pyramid.view import view_config
25
28 from pyramid.response import Response
26 from rhodecode.model.scm import UserGroupList
29 from pyramid.renderers import render
27
30
28 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous,
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
31 HasUserGroupPermissionAnyDecorator)
34 from rhodecode.lib import helpers as h, audit_logger
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.utils import PartialRenderer
35 from rhodecode.lib.utils import PartialRenderer
34 from rhodecode.lib.utils2 import safe_unicode
36 from rhodecode.lib.utils2 import safe_unicode
37
38 from rhodecode.model.forms import UserGroupForm
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.scm import UserGroupList
35 from rhodecode.model.db import (
41 from rhodecode.model.db import (
36 joinedload, or_, count, User, UserGroup, UserGroupMember,
42 or_, count, User, UserGroup, UserGroupMember)
37 UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
38 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.user_group import UserGroupModel
39
45
40 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
41
47
42
48
43 class AdminUserGroupsView(BaseAppView, DataGridAppView):
49 class AdminUserGroupsView(BaseAppView, DataGridAppView):
44
50
45 def load_default_context(self):
51 def load_default_context(self):
46 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
53
54 PermissionModel().set_global_permission_choices(
55 c, gettext_translator=self.request.translate)
56
47 self._register_global_c(c)
57 self._register_global_c(c)
48 return c
58 return c
49
59
50 # permission check in data loading of
60 # permission check in data loading of
51 # `user_groups_list_data` via UserGroupList
61 # `user_groups_list_data` via UserGroupList
52 @LoginRequired()
62 @LoginRequired()
53 @NotAnonymous()
63 @NotAnonymous()
54 @view_config(
64 @view_config(
55 route_name='user_groups', request_method='GET',
65 route_name='user_groups', request_method='GET',
56 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
57 def user_groups_list(self):
67 def user_groups_list(self):
58 c = self.load_default_context()
68 c = self.load_default_context()
59 return self._get_template_context(c)
69 return self._get_template_context(c)
60
70
61 # permission check inside
71 # permission check inside
62 @LoginRequired()
72 @LoginRequired()
63 @NotAnonymous()
73 @NotAnonymous()
64 @view_config(
74 @view_config(
65 route_name='user_groups_data', request_method='GET',
75 route_name='user_groups_data', request_method='GET',
66 renderer='json_ext', xhr=True)
76 renderer='json_ext', xhr=True)
67 def user_groups_list_data(self):
77 def user_groups_list_data(self):
68 column_map = {
78 column_map = {
69 'active': 'users_group_active',
79 'active': 'users_group_active',
70 'description': 'user_group_description',
80 'description': 'user_group_description',
71 'members': 'members_total',
81 'members': 'members_total',
72 'owner': 'user_username',
82 'owner': 'user_username',
73 'sync': 'group_data'
83 'sync': 'group_data'
74 }
84 }
75 draw, start, limit = self._extract_chunk(self.request)
85 draw, start, limit = self._extract_chunk(self.request)
76 search_q, order_by, order_dir = self._extract_ordering(
86 search_q, order_by, order_dir = self._extract_ordering(
77 self.request, column_map=column_map)
87 self.request, column_map=column_map)
78
88
79 _render = PartialRenderer('data_table/_dt_elements.mako')
89 _render = PartialRenderer('data_table/_dt_elements.mako')
80
90
81 def user_group_name(user_group_id, user_group_name):
91 def user_group_name(user_group_id, user_group_name):
82 return _render("user_group_name", user_group_id, user_group_name)
92 return _render("user_group_name", user_group_id, user_group_name)
83
93
84 def user_group_actions(user_group_id, user_group_name):
94 def user_group_actions(user_group_id, user_group_name):
85 return _render("user_group_actions", user_group_id, user_group_name)
95 return _render("user_group_actions", user_group_id, user_group_name)
86
96
87 def user_profile(username):
97 def user_profile(username):
88 return _render('user_profile', username)
98 return _render('user_profile', username)
89
99
90 auth_user_group_list = UserGroupList(
100 auth_user_group_list = UserGroupList(
91 UserGroup.query().all(), perm_set=['usergroup.admin'])
101 UserGroup.query().all(), perm_set=['usergroup.admin'])
92
102
93 allowed_ids = []
103 allowed_ids = []
94 for user_group in auth_user_group_list:
104 for user_group in auth_user_group_list:
95 allowed_ids.append(user_group.users_group_id)
105 allowed_ids.append(user_group.users_group_id)
96
106
97 user_groups_data_total_count = UserGroup.query()\
107 user_groups_data_total_count = UserGroup.query()\
98 .filter(UserGroup.users_group_id.in_(allowed_ids))\
108 .filter(UserGroup.users_group_id.in_(allowed_ids))\
99 .count()
109 .count()
100
110
101 member_count = count(UserGroupMember.user_id)
111 member_count = count(UserGroupMember.user_id)
102 base_q = Session.query(
112 base_q = Session.query(
103 UserGroup.users_group_name,
113 UserGroup.users_group_name,
104 UserGroup.user_group_description,
114 UserGroup.user_group_description,
105 UserGroup.users_group_active,
115 UserGroup.users_group_active,
106 UserGroup.users_group_id,
116 UserGroup.users_group_id,
107 UserGroup.group_data,
117 UserGroup.group_data,
108 User,
118 User,
109 member_count.label('member_count')
119 member_count.label('member_count')
110 ) \
120 ) \
111 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
121 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
112 .outerjoin(UserGroupMember) \
122 .outerjoin(UserGroupMember) \
113 .join(User, User.user_id == UserGroup.user_id) \
123 .join(User, User.user_id == UserGroup.user_id) \
114 .group_by(UserGroup, User)
124 .group_by(UserGroup, User)
115
125
116 if search_q:
126 if search_q:
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
127 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 base_q = base_q.filter(or_(
128 base_q = base_q.filter(or_(
119 UserGroup.users_group_name.ilike(like_expression),
129 UserGroup.users_group_name.ilike(like_expression),
120 ))
130 ))
121
131
122 user_groups_data_total_filtered_count = base_q.count()
132 user_groups_data_total_filtered_count = base_q.count()
123
133
124 if order_by == 'members_total':
134 if order_by == 'members_total':
125 sort_col = member_count
135 sort_col = member_count
126 elif order_by == 'user_username':
136 elif order_by == 'user_username':
127 sort_col = User.username
137 sort_col = User.username
128 else:
138 else:
129 sort_col = getattr(UserGroup, order_by, None)
139 sort_col = getattr(UserGroup, order_by, None)
130
140
131 if isinstance(sort_col, count) or sort_col:
141 if isinstance(sort_col, count) or sort_col:
132 if order_dir == 'asc':
142 if order_dir == 'asc':
133 sort_col = sort_col.asc()
143 sort_col = sort_col.asc()
134 else:
144 else:
135 sort_col = sort_col.desc()
145 sort_col = sort_col.desc()
136
146
137 base_q = base_q.order_by(sort_col)
147 base_q = base_q.order_by(sort_col)
138 base_q = base_q.offset(start).limit(limit)
148 base_q = base_q.offset(start).limit(limit)
139
149
140 # authenticated access to user groups
150 # authenticated access to user groups
141 auth_user_group_list = base_q.all()
151 auth_user_group_list = base_q.all()
142
152
143 user_groups_data = []
153 user_groups_data = []
144 for user_gr in auth_user_group_list:
154 for user_gr in auth_user_group_list:
145 user_groups_data.append({
155 user_groups_data.append({
146 "users_group_name": user_group_name(
156 "users_group_name": user_group_name(
147 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
157 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
148 "name_raw": h.escape(user_gr.users_group_name),
158 "name_raw": h.escape(user_gr.users_group_name),
149 "description": h.escape(user_gr.user_group_description),
159 "description": h.escape(user_gr.user_group_description),
150 "members": user_gr.member_count,
160 "members": user_gr.member_count,
151 # NOTE(marcink): because of advanced query we
161 # NOTE(marcink): because of advanced query we
152 # need to load it like that
162 # need to load it like that
153 "sync": UserGroup._load_group_data(
163 "sync": UserGroup._load_group_data(
154 user_gr.group_data).get('extern_type'),
164 user_gr.group_data).get('extern_type'),
155 "active": h.bool2icon(user_gr.users_group_active),
165 "active": h.bool2icon(user_gr.users_group_active),
156 "owner": user_profile(user_gr.User.username),
166 "owner": user_profile(user_gr.User.username),
157 "action": user_group_actions(
167 "action": user_group_actions(
158 user_gr.users_group_id, user_gr.users_group_name)
168 user_gr.users_group_id, user_gr.users_group_name)
159 })
169 })
160
170
161 data = ({
171 data = ({
162 'draw': draw,
172 'draw': draw,
163 'data': user_groups_data,
173 'data': user_groups_data,
164 'recordsTotal': user_groups_data_total_count,
174 'recordsTotal': user_groups_data_total_count,
165 'recordsFiltered': user_groups_data_total_filtered_count,
175 'recordsFiltered': user_groups_data_total_filtered_count,
166 })
176 })
167
177
168 return data
178 return data
169
179
170 @LoginRequired()
180 @LoginRequired()
171 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
181 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
172 @view_config(
182 @view_config(
173 route_name='user_group_members_data', request_method='GET',
183 route_name='user_groups_new', request_method='GET',
174 renderer='json_ext', xhr=True)
184 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
175 def user_group_members(self):
185 def user_groups_new(self):
176 """
177 Return members of given user group
178 """
179 user_group_id = self.request.matchdict['user_group_id']
180 user_group = UserGroup.get_or_404(user_group_id)
181 group_members_obj = sorted((x.user for x in user_group.members),
182 key=lambda u: u.username.lower())
183
184 group_members = [
185 {
186 'id': user.user_id,
187 'first_name': user.first_name,
188 'last_name': user.last_name,
189 'username': user.username,
190 'icon_link': h.gravatar_url(user.email, 30),
191 'value_display': h.person(user.email),
192 'value': user.username,
193 'value_type': 'user',
194 'active': user.active,
195 }
196 for user in group_members_obj
197 ]
198
199 return {
200 'members': group_members
201 }
202
203 def _get_perms_summary(self, user_group_id):
204 permissions = {
205 'repositories': {},
206 'repositories_groups': {},
207 }
208 ugroup_repo_perms = UserGroupRepoToPerm.query()\
209 .options(joinedload(UserGroupRepoToPerm.permission))\
210 .options(joinedload(UserGroupRepoToPerm.repository))\
211 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
212 .all()
213
214 for gr in ugroup_repo_perms:
215 permissions['repositories'][gr.repository.repo_name] \
216 = gr.permission.permission_name
217
218 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
219 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
220 .options(joinedload(UserGroupRepoGroupToPerm.group))\
221 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
222 .all()
223
224 for gr in ugroup_group_perms:
225 permissions['repositories_groups'][gr.group.group_name] \
226 = gr.permission.permission_name
227 return permissions
228
229 @LoginRequired()
230 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
231 @view_config(
232 route_name='edit_user_group_perms_summary', request_method='GET',
233 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
234 def user_group_perms_summary(self):
235 c = self.load_default_context()
186 c = self.load_default_context()
236
237 user_group_id = self.request.matchdict.get('user_group_id')
238 c.user_group = UserGroup.get_or_404(user_group_id)
239
240 c.active = 'perms_summary'
241
242 c.permissions = self._get_perms_summary(c.user_group.users_group_id)
243 return self._get_template_context(c)
187 return self._get_template_context(c)
244
188
245 @LoginRequired()
189 @LoginRequired()
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
190 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
191 @CSRFRequired()
247 @view_config(
192 @view_config(
248 route_name='edit_user_group_perms_summary_json', request_method='GET',
193 route_name='user_groups_create', request_method='POST',
249 renderer='json_ext')
194 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
250 def user_group_perms_summary_json(self):
195 def user_groups_create(self):
251 self.load_default_context()
196 _ = self.request.translate
197 c = self.load_default_context()
198 users_group_form = UserGroupForm()()
199
200 user_group_name = self.request.POST.get('users_group_name')
201 try:
202 form_result = users_group_form.to_python(dict(self.request.POST))
203 user_group = UserGroupModel().create(
204 name=form_result['users_group_name'],
205 description=form_result['user_group_description'],
206 owner=self._rhodecode_user.user_id,
207 active=form_result['users_group_active'])
208 Session().flush()
209 creation_data = user_group.get_api_data()
210 user_group_name = form_result['users_group_name']
211
212 audit_logger.store_web(
213 'user_group.create', action_data={'data': creation_data},
214 user=self._rhodecode_user)
252
215
253 user_group_id = self.request.matchdict.get('user_group_id')
216 user_group_link = h.link_to(
254 user_group = UserGroup.get_or_404(user_group_id)
217 h.escape(user_group_name),
218 h.route_path(
219 'edit_user_group', user_group_id=user_group.users_group_id))
220 h.flash(h.literal(_('Created user group %(user_group_link)s')
221 % {'user_group_link': user_group_link}),
222 category='success')
223 Session().commit()
224 user_group_id = user_group.users_group_id
225 except formencode.Invalid as errors:
255
226
256 return self._get_perms_summary(user_group.users_group_id)
227 data = render(
228 'rhodecode:templates/admin/user_groups/user_group_add.mako',
229 self._get_template_context(c), self.request)
230 html = formencode.htmlfill.render(
231 data,
232 defaults=errors.value,
233 errors=errors.error_dict or {},
234 prefix_error=False,
235 encoding="UTF-8",
236 force_defaults=False
237 )
238 return Response(html)
239
240 except Exception:
241 log.exception("Exception creating user group")
242 h.flash(_('Error occurred during creation of user group %s') \
243 % user_group_name, category='error')
244 raise HTTPFound(h.route_path('user_groups_new'))
245
246 raise HTTPFound(
247 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,225 +1,254 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
24 TestController, assert_session_flash, TEST_USER_ADMIN_LOGIN)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 TEST_USER_GROUP = 'admins_test'
30
31 fixture = Fixture()
29 fixture = Fixture()
32
30
33
31
34 class TestAdminUsersGroupsController(TestController):
32 def route_path(name, params=None, **kwargs):
35
33 import urllib
36 def test_create(self):
34 from rhodecode.apps._base import ADMIN_PREFIX
37 self.log_user()
38 users_group_name = TEST_USER_GROUP
39 response = self.app.post(url('users_groups'), {
40 'users_group_name': users_group_name,
41 'user_group_description': 'DESC',
42 'active': True,
43 'csrf_token': self.csrf_token})
44
35
45 user_group_link = link_to(
36 base_url = {
46 users_group_name,
37 'user_groups': ADMIN_PREFIX + '/user_groups',
47 url('edit_users_group',
38 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
48 user_group_id=UserGroup.get_by_group_name(
39 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
49 users_group_name).users_group_id))
40 'user_groups_new': ADMIN_PREFIX + '/user_groups/new',
50 assert_session_flash(
41 'user_groups_create': ADMIN_PREFIX + '/user_groups/create',
51 response,
42 'edit_user_group': ADMIN_PREFIX + '/user_groups/{user_group_id}/edit',
52 'Created user group %s' % user_group_link)
43 'edit_user_group_advanced_sync': ADMIN_PREFIX + '/user_groups/{user_group_id}/edit/advanced/sync',
44 'edit_user_group_global_perms_update': ADMIN_PREFIX + '/user_groups/{user_group_id}/edit/global_permissions/update',
45 'user_groups_update': ADMIN_PREFIX + '/user_groups/{user_group_id}/update',
46 'user_groups_delete': ADMIN_PREFIX + '/user_groups/{user_group_id}/delete',
53
47
54 def test_set_synchronization(self):
48 }[name].format(**kwargs)
49
50 if params:
51 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
52 return base_url
53
54
55 class TestUserGroupsView(TestController):
56
57 def test_set_synchronization(self, user_util):
55 self.log_user()
58 self.log_user()
56 users_group_name = TEST_USER_GROUP + 'sync'
59 user_group_name = user_util.create_user_group().users_group_name
57 response = self.app.post(url('users_groups'), {
58 'users_group_name': users_group_name,
59 'user_group_description': 'DESC',
60 'active': True,
61 'csrf_token': self.csrf_token})
62
60
63 group = Session().query(UserGroup).filter(
61 group = Session().query(UserGroup).filter(
64 UserGroup.users_group_name == users_group_name).one()
62 UserGroup.users_group_name == user_group_name).one()
65
63
66 assert group.group_data.get('extern_type') is None
64 assert group.group_data.get('extern_type') is None
67
65
68 # enable
66 # enable
69 self.app.post(
67 self.app.post(
70 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
68 route_path('edit_user_group_advanced_sync',
69 user_group_id=group.users_group_id),
71 params={'csrf_token': self.csrf_token}, status=302)
70 params={'csrf_token': self.csrf_token}, status=302)
72
71
73 group = Session().query(UserGroup).filter(
72 group = Session().query(UserGroup).filter(
74 UserGroup.users_group_name == users_group_name).one()
73 UserGroup.users_group_name == user_group_name).one()
75 assert group.group_data.get('extern_type') == 'manual'
74 assert group.group_data.get('extern_type') == 'manual'
76 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
75 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
77
76
78 # disable
77 # disable
79 self.app.post(
78 self.app.post(
80 url('edit_user_group_advanced_sync',
79 route_path('edit_user_group_advanced_sync',
81 user_group_id=group.users_group_id),
80 user_group_id=group.users_group_id),
82 params={'csrf_token': self.csrf_token}, status=302)
81 params={'csrf_token': self.csrf_token}, status=302)
83
82
84 group = Session().query(UserGroup).filter(
83 group = Session().query(UserGroup).filter(
85 UserGroup.users_group_name == users_group_name).one()
84 UserGroup.users_group_name == user_group_name).one()
86 assert group.group_data.get('extern_type') is None
85 assert group.group_data.get('extern_type') is None
87 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
86 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
88
87
89 def test_delete(self):
88 def test_delete_user_group(self, user_util):
90 self.log_user()
89 self.log_user()
91 users_group_name = TEST_USER_GROUP + 'another'
90 user_group_id = user_util.create_user_group().users_group_id
92 response = self.app.post(url('users_groups'), {
93 'users_group_name': users_group_name,
94 'user_group_description': 'DESC',
95 'active': True,
96 'csrf_token': self.csrf_token})
97
98 user_group_link = link_to(
99 users_group_name,
100 url('edit_users_group',
101 user_group_id=UserGroup.get_by_group_name(
102 users_group_name).users_group_id))
103 assert_session_flash(
104 response,
105 'Created user group %s' % user_group_link)
106
91
107 group = Session().query(UserGroup).filter(
92 group = Session().query(UserGroup).filter(
108 UserGroup.users_group_name == users_group_name).one()
93 UserGroup.users_group_id == user_group_id).one()
109
94
110 self.app.post(
95 self.app.post(
111 url('delete_users_group', user_group_id=group.users_group_id),
96 route_path('user_groups_delete', user_group_id=group.users_group_id),
112 params={'_method': 'delete', 'csrf_token': self.csrf_token})
97 params={'csrf_token': self.csrf_token})
113
98
114 group = Session().query(UserGroup).filter(
99 group = Session().query(UserGroup).filter(
115 UserGroup.users_group_name == users_group_name).scalar()
100 UserGroup.users_group_id == user_group_id).scalar()
116
101
117 assert group is None
102 assert group is None
118
103
119 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
104 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
120 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
105 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
121 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
106 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
122 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
107 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
123 ('', '', '', '', '', '', True, False),
108 ('', '', '', '', '', '', True, False),
124 ])
109 ])
125 def test_global_perms_on_group(
110 def test_global_permissions_on_user_group(
126 self, repo_create, repo_create_write, user_group_create,
111 self, repo_create, repo_create_write, user_group_create,
127 repo_group_create, fork_create, expect_error, expect_form_error,
112 repo_group_create, fork_create, expect_error, expect_form_error,
128 inherit_default_permissions):
113 inherit_default_permissions, user_util):
129 self.log_user()
130 users_group_name = TEST_USER_GROUP + 'another2'
131 response = self.app.post(url('users_groups'),
132 {'users_group_name': users_group_name,
133 'user_group_description': 'DESC',
134 'active': True,
135 'csrf_token': self.csrf_token})
136
114
137 ug = UserGroup.get_by_group_name(users_group_name)
115 self.log_user()
138 user_group_link = link_to(
116 user_group = user_util.create_user_group()
139 users_group_name,
117
140 url('edit_users_group', user_group_id=ug.users_group_id))
118 user_group_name = user_group.users_group_name
141 assert_session_flash(
119 user_group_id = user_group.users_group_id
142 response,
143 'Created user group %s' % user_group_link)
144 response.follow()
145
120
146 # ENABLE REPO CREATE ON A GROUP
121 # ENABLE REPO CREATE ON A GROUP
147 perm_params = {
122 perm_params = {
148 'inherit_default_permissions': False,
123 'inherit_default_permissions': False,
149 'default_repo_create': repo_create,
124 'default_repo_create': repo_create,
150 'default_repo_create_on_write': repo_create_write,
125 'default_repo_create_on_write': repo_create_write,
151 'default_user_group_create': user_group_create,
126 'default_user_group_create': user_group_create,
152 'default_repo_group_create': repo_group_create,
127 'default_repo_group_create': repo_group_create,
153 'default_fork_create': fork_create,
128 'default_fork_create': fork_create,
154 'default_inherit_default_permissions': inherit_default_permissions,
129 'default_inherit_default_permissions': inherit_default_permissions,
155
130
156 '_method': 'put',
157 'csrf_token': self.csrf_token,
131 'csrf_token': self.csrf_token,
158 }
132 }
159 response = self.app.post(
133 response = self.app.post(
160 url('edit_user_group_global_perms',
134 route_path('edit_user_group_global_perms_update',
161 user_group_id=ug.users_group_id),
135 user_group_id=user_group_id),
162 params=perm_params)
136 params=perm_params)
163
137
164 if expect_form_error:
138 if expect_form_error:
165 assert response.status_int == 200
139 assert response.status_int == 200
166 response.mustcontain('Value must be one of')
140 response.mustcontain('Value must be one of')
167 else:
141 else:
168 if expect_error:
142 if expect_error:
169 msg = 'An error occurred during permissions saving'
143 msg = 'An error occurred during permissions saving'
170 else:
144 else:
171 msg = 'User Group global permissions updated successfully'
145 msg = 'User Group global permissions updated successfully'
172 ug = UserGroup.get_by_group_name(users_group_name)
146 ug = UserGroup.get_by_group_name(user_group_name)
173 del perm_params['_method']
174 del perm_params['csrf_token']
147 del perm_params['csrf_token']
175 del perm_params['inherit_default_permissions']
148 del perm_params['inherit_default_permissions']
176 assert perm_params == ug.get_default_perms()
149 assert perm_params == ug.get_default_perms()
177 assert_session_flash(response, msg)
150 assert_session_flash(response, msg)
178
151
179 fixture.destroy_user_group(users_group_name)
152 def test_edit_view(self, user_util):
180
181 def test_edit_autocomplete(self):
182 self.log_user()
153 self.log_user()
183 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
154
184 response = self.app.get(
155 user_group = user_util.create_user_group()
185 url('edit_users_group', user_group_id=ug.users_group_id))
156 self.app.get(
186 fixture.destroy_user_group(TEST_USER_GROUP)
157 route_path('edit_user_group',
158 user_group_id=user_group.users_group_id),
159 status=200)
160
161 def test_update_user_group(self, user_util):
162 user = self.log_user()
163
164 user_group = user_util.create_user_group()
165 users_group_id = user_group.users_group_id
166 new_name = user_group.users_group_name + '_CHANGE'
167
168 params = [
169 ('users_group_active', False),
170 ('user_group_description', 'DESC'),
171 ('users_group_name', new_name),
172 ('user', user['username']),
173 ('csrf_token', self.csrf_token),
174 ('__start__', 'user_group_members:sequence'),
175 ('__start__', 'member:mapping'),
176 ('member_user_id', user['user_id']),
177 ('type', 'existing'),
178 ('__end__', 'member:mapping'),
179 ('__end__', 'user_group_members:sequence'),
180 ]
181
182 self.app.post(
183 route_path('user_groups_update',
184 user_group_id=users_group_id),
185 params=params,
186 status=302)
187
188 user_group = UserGroup.get(users_group_id)
189 assert user_group
190
191 assert user_group.users_group_name == new_name
192 assert user_group.user_group_description == 'DESC'
193 assert user_group.users_group_active == False
194
195 def test_update_user_group_name_conflicts(self, user_util):
196 self.log_user()
197 user_group_old = user_util.create_user_group()
198 new_name = user_group_old.users_group_name
199
200 user_group = user_util.create_user_group()
201
202 params = dict(
203 users_group_active=False,
204 user_group_description='DESC',
205 users_group_name=new_name,
206 csrf_token=self.csrf_token)
207
208 response = self.app.post(
209 route_path('user_groups_update',
210 user_group_id=user_group.users_group_id),
211 params=params,
212 status=200)
213
214 response.mustcontain('User group `{}` already exists'.format(
215 new_name))
187
216
188 def test_update_members_from_user_ids(self, user_regular):
217 def test_update_members_from_user_ids(self, user_regular):
189 uid = user_regular.user_id
218 uid = user_regular.user_id
190 username = user_regular.username
219 username = user_regular.username
191 self.log_user()
220 self.log_user()
192
221
193 user_group = fixture.create_user_group('test_gr_ids')
222 user_group = fixture.create_user_group('test_gr_ids')
194 assert user_group.members == []
223 assert user_group.members == []
195 assert user_group.user != user_regular
224 assert user_group.user != user_regular
196 expected_active_state = not user_group.users_group_active
225 expected_active_state = not user_group.users_group_active
197
226
198 form_data = [
227 form_data = [
199 ('csrf_token', self.csrf_token),
228 ('csrf_token', self.csrf_token),
200 ('_method', 'put'),
201 ('user', username),
229 ('user', username),
202 ('users_group_name', 'changed_name'),
230 ('users_group_name', 'changed_name'),
203 ('users_group_active', expected_active_state),
231 ('users_group_active', expected_active_state),
204 ('user_group_description', 'changed_description'),
232 ('user_group_description', 'changed_description'),
205
233
206 ('__start__', 'user_group_members:sequence'),
234 ('__start__', 'user_group_members:sequence'),
207 ('__start__', 'member:mapping'),
235 ('__start__', 'member:mapping'),
208 ('member_user_id', uid),
236 ('member_user_id', uid),
209 ('type', 'existing'),
237 ('type', 'existing'),
210 ('__end__', 'member:mapping'),
238 ('__end__', 'member:mapping'),
211 ('__end__', 'user_group_members:sequence'),
239 ('__end__', 'user_group_members:sequence'),
212 ]
240 ]
213 ugid = user_group.users_group_id
241 ugid = user_group.users_group_id
214 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
242 self.app.post(
243 route_path('user_groups_update', user_group_id=ugid), form_data)
215
244
216 user_group = UserGroup.get(ugid)
245 user_group = UserGroup.get(ugid)
217 assert user_group
246 assert user_group
218
247
219 assert user_group.members[0].user_id == uid
248 assert user_group.members[0].user_id == uid
220 assert user_group.user_id == uid
249 assert user_group.user_id == uid
221 assert 'changed_name' in user_group.users_group_name
250 assert 'changed_name' in user_group.users_group_name
222 assert 'changed_description' in user_group.user_group_description
251 assert 'changed_description' in user_group.user_group_description
223 assert user_group.users_group_active == expected_active_state
252 assert user_group.users_group_active == expected_active_state
224
253
225 fixture.destroy_user_group(user_group)
254 fixture.destroy_user_group(user_group)
@@ -1,535 +1,536 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25 import traceback
25 import traceback
26 from collections import OrderedDict
26 from collections import OrderedDict
27
27
28 from paste.registry import RegistryManager
28 from paste.registry import RegistryManager
29 from paste.gzipper import make_gzip_middleware
29 from paste.gzipper import make_gzip_middleware
30 from pylons.wsgiapp import PylonsApp
30 from pylons.wsgiapp import PylonsApp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
35 from pyramid.httpexceptions import (
35 from pyramid.httpexceptions import (
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 from pyramid.events import ApplicationCreated
37 from pyramid.events import ApplicationCreated
38 from pyramid.renderers import render_to_response
38 from pyramid.renderers import render_to_response
39 from routes.middleware import RoutesMiddleware
39 from routes.middleware import RoutesMiddleware
40 import rhodecode
40 import rhodecode
41
41
42 from rhodecode.model import meta
42 from rhodecode.model import meta
43 from rhodecode.config import patches
43 from rhodecode.config import patches
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.environment import (
45 from rhodecode.config.environment import (
46 load_environment, load_pyramid_environment)
46 load_environment, load_pyramid_environment)
47
47
48 from rhodecode.lib.vcs import VCSCommunicationError
48 from rhodecode.lib.vcs import VCSCommunicationError
49 from rhodecode.lib.exceptions import VCSServerUnavailable
49 from rhodecode.lib.exceptions import VCSServerUnavailable
50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 from rhodecode.lib.middleware.error_handling import (
51 from rhodecode.lib.middleware.error_handling import (
52 PylonsErrorHandlingMiddleware)
52 PylonsErrorHandlingMiddleware)
53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 from rhodecode.lib.middleware.vcs import VCSMiddleware
54 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 from rhodecode.subscribers import (
57 from rhodecode.subscribers import (
58 scan_repositories_if_enabled, write_js_routes_if_enabled,
58 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 write_metadata_if_needed)
59 write_metadata_if_needed)
60
60
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 # for certain routes which won't go to pylons to - eg. static files, debugger
66 # for certain routes which won't go to pylons to - eg. static files, debugger
67 # it is only needed for the pylons migration and can be removed once complete
67 # it is only needed for the pylons migration and can be removed once complete
68 class SkippableRoutesMiddleware(RoutesMiddleware):
68 class SkippableRoutesMiddleware(RoutesMiddleware):
69 """ Routes middleware that allows you to skip prefixes """
69 """ Routes middleware that allows you to skip prefixes """
70
70
71 def __init__(self, *args, **kw):
71 def __init__(self, *args, **kw):
72 self.skip_prefixes = kw.pop('skip_prefixes', [])
72 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74
74
75 def __call__(self, environ, start_response):
75 def __call__(self, environ, start_response):
76 for prefix in self.skip_prefixes:
76 for prefix in self.skip_prefixes:
77 if environ['PATH_INFO'].startswith(prefix):
77 if environ['PATH_INFO'].startswith(prefix):
78 # added to avoid the case when a missing /_static route falls
78 # added to avoid the case when a missing /_static route falls
79 # through to pylons and causes an exception as pylons is
79 # through to pylons and causes an exception as pylons is
80 # expecting wsgiorg.routingargs to be set in the environ
80 # expecting wsgiorg.routingargs to be set in the environ
81 # by RoutesMiddleware.
81 # by RoutesMiddleware.
82 if 'wsgiorg.routing_args' not in environ:
82 if 'wsgiorg.routing_args' not in environ:
83 environ['wsgiorg.routing_args'] = (None, {})
83 environ['wsgiorg.routing_args'] = (None, {})
84 return self.app(environ, start_response)
84 return self.app(environ, start_response)
85
85
86 return super(SkippableRoutesMiddleware, self).__call__(
86 return super(SkippableRoutesMiddleware, self).__call__(
87 environ, start_response)
87 environ, start_response)
88
88
89
89
90 def make_app(global_conf, static_files=True, **app_conf):
90 def make_app(global_conf, static_files=True, **app_conf):
91 """Create a Pylons WSGI application and return it
91 """Create a Pylons WSGI application and return it
92
92
93 ``global_conf``
93 ``global_conf``
94 The inherited configuration for this application. Normally from
94 The inherited configuration for this application. Normally from
95 the [DEFAULT] section of the Paste ini file.
95 the [DEFAULT] section of the Paste ini file.
96
96
97 ``app_conf``
97 ``app_conf``
98 The application's local configuration. Normally specified in
98 The application's local configuration. Normally specified in
99 the [app:<name>] section of the Paste ini file (where <name>
99 the [app:<name>] section of the Paste ini file (where <name>
100 defaults to main).
100 defaults to main).
101
101
102 """
102 """
103 # Apply compatibility patches
103 # Apply compatibility patches
104 patches.kombu_1_5_1_python_2_7_11()
104 patches.kombu_1_5_1_python_2_7_11()
105 patches.inspect_getargspec()
105 patches.inspect_getargspec()
106
106
107 # Configure the Pylons environment
107 # Configure the Pylons environment
108 config = load_environment(global_conf, app_conf)
108 config = load_environment(global_conf, app_conf)
109
109
110 # The Pylons WSGI app
110 # The Pylons WSGI app
111 app = PylonsApp(config=config)
111 app = PylonsApp(config=config)
112
112
113 # Establish the Registry for this application
113 # Establish the Registry for this application
114 app = RegistryManager(app)
114 app = RegistryManager(app)
115
115
116 app.config = config
116 app.config = config
117
117
118 return app
118 return app
119
119
120
120
121 def make_pyramid_app(global_config, **settings):
121 def make_pyramid_app(global_config, **settings):
122 """
122 """
123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 application.
124 application.
125
125
126 Specials:
126 Specials:
127
127
128 * We migrate from Pylons to Pyramid. While doing this, we keep both
128 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 frameworks functional. This involves moving some WSGI middlewares around
129 frameworks functional. This involves moving some WSGI middlewares around
130 and providing access to some data internals, so that the old code is
130 and providing access to some data internals, so that the old code is
131 still functional.
131 still functional.
132
132
133 * The application can also be integrated like a plugin via the call to
133 * The application can also be integrated like a plugin via the call to
134 `includeme`. This is accompanied with the other utility functions which
134 `includeme`. This is accompanied with the other utility functions which
135 are called. Changing this should be done with great care to not break
135 are called. Changing this should be done with great care to not break
136 cases when these fragments are assembled from another place.
136 cases when these fragments are assembled from another place.
137
137
138 """
138 """
139 # The edition string should be available in pylons too, so we add it here
139 # The edition string should be available in pylons too, so we add it here
140 # before copying the settings.
140 # before copying the settings.
141 settings.setdefault('rhodecode.edition', 'Community Edition')
141 settings.setdefault('rhodecode.edition', 'Community Edition')
142
142
143 # As long as our Pylons application does expect "unprepared" settings, make
143 # As long as our Pylons application does expect "unprepared" settings, make
144 # sure that we keep an unmodified copy. This avoids unintentional change of
144 # sure that we keep an unmodified copy. This avoids unintentional change of
145 # behavior in the old application.
145 # behavior in the old application.
146 settings_pylons = settings.copy()
146 settings_pylons = settings.copy()
147
147
148 sanitize_settings_and_apply_defaults(settings)
148 sanitize_settings_and_apply_defaults(settings)
149 config = Configurator(settings=settings)
149 config = Configurator(settings=settings)
150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
151
151
152 load_pyramid_environment(global_config, settings)
152 load_pyramid_environment(global_config, settings)
153
153
154 includeme_first(config)
154 includeme_first(config)
155 includeme(config)
155 includeme(config)
156
156
157 pyramid_app = config.make_wsgi_app()
157 pyramid_app = config.make_wsgi_app()
158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
159 pyramid_app.config = config
159 pyramid_app.config = config
160
160
161 # creating the app uses a connection - return it after we are done
161 # creating the app uses a connection - return it after we are done
162 meta.Session.remove()
162 meta.Session.remove()
163
163
164 return pyramid_app
164 return pyramid_app
165
165
166
166
167 def make_not_found_view(config):
167 def make_not_found_view(config):
168 """
168 """
169 This creates the view which should be registered as not-found-view to
169 This creates the view which should be registered as not-found-view to
170 pyramid. Basically it contains of the old pylons app, converted to a view.
170 pyramid. Basically it contains of the old pylons app, converted to a view.
171 Additionally it is wrapped by some other middlewares.
171 Additionally it is wrapped by some other middlewares.
172 """
172 """
173 settings = config.registry.settings
173 settings = config.registry.settings
174 vcs_server_enabled = settings['vcs.server.enable']
174 vcs_server_enabled = settings['vcs.server.enable']
175
175
176 # Make pylons app from unprepared settings.
176 # Make pylons app from unprepared settings.
177 pylons_app = make_app(
177 pylons_app = make_app(
178 config.registry._pylons_compat_global_config,
178 config.registry._pylons_compat_global_config,
179 **config.registry._pylons_compat_settings)
179 **config.registry._pylons_compat_settings)
180 config.registry._pylons_compat_config = pylons_app.config
180 config.registry._pylons_compat_config = pylons_app.config
181
181
182 # Appenlight monitoring.
182 # Appenlight monitoring.
183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
184 pylons_app, settings)
184 pylons_app, settings)
185
185
186 # The pylons app is executed inside of the pyramid 404 exception handler.
186 # The pylons app is executed inside of the pyramid 404 exception handler.
187 # Exceptions which are raised inside of it are not handled by pyramid
187 # Exceptions which are raised inside of it are not handled by pyramid
188 # again. Therefore we add a middleware that invokes the error handler in
188 # again. Therefore we add a middleware that invokes the error handler in
189 # case of an exception or error response. This way we return proper error
189 # case of an exception or error response. This way we return proper error
190 # HTML pages in case of an error.
190 # HTML pages in case of an error.
191 reraise = (settings.get('debugtoolbar.enabled', False) or
191 reraise = (settings.get('debugtoolbar.enabled', False) or
192 rhodecode.disable_error_handler)
192 rhodecode.disable_error_handler)
193 pylons_app = PylonsErrorHandlingMiddleware(
193 pylons_app = PylonsErrorHandlingMiddleware(
194 pylons_app, error_handler, reraise)
194 pylons_app, error_handler, reraise)
195
195
196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
197 # view to handle the request. Therefore it is wrapped around the pylons
197 # view to handle the request. Therefore it is wrapped around the pylons
198 # app. It has to be outside of the error handling otherwise error responses
198 # app. It has to be outside of the error handling otherwise error responses
199 # from the vcsserver are converted to HTML error pages. This confuses the
199 # from the vcsserver are converted to HTML error pages. This confuses the
200 # command line tools and the user won't get a meaningful error message.
200 # command line tools and the user won't get a meaningful error message.
201 if vcs_server_enabled:
201 if vcs_server_enabled:
202 pylons_app = VCSMiddleware(
202 pylons_app = VCSMiddleware(
203 pylons_app, settings, appenlight_client, registry=config.registry)
203 pylons_app, settings, appenlight_client, registry=config.registry)
204
204
205 # Convert WSGI app to pyramid view and return it.
205 # Convert WSGI app to pyramid view and return it.
206 return wsgiapp(pylons_app)
206 return wsgiapp(pylons_app)
207
207
208
208
209 def add_pylons_compat_data(registry, global_config, settings):
209 def add_pylons_compat_data(registry, global_config, settings):
210 """
210 """
211 Attach data to the registry to support the Pylons integration.
211 Attach data to the registry to support the Pylons integration.
212 """
212 """
213 registry._pylons_compat_global_config = global_config
213 registry._pylons_compat_global_config = global_config
214 registry._pylons_compat_settings = settings
214 registry._pylons_compat_settings = settings
215
215
216
216
217 def error_handler(exception, request):
217 def error_handler(exception, request):
218 import rhodecode
218 import rhodecode
219 from rhodecode.lib import helpers
219 from rhodecode.lib import helpers
220
220
221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
222
222
223 base_response = HTTPInternalServerError()
223 base_response = HTTPInternalServerError()
224 # prefer original exception for the response since it may have headers set
224 # prefer original exception for the response since it may have headers set
225 if isinstance(exception, HTTPException):
225 if isinstance(exception, HTTPException):
226 base_response = exception
226 base_response = exception
227 elif isinstance(exception, VCSCommunicationError):
227 elif isinstance(exception, VCSCommunicationError):
228 base_response = VCSServerUnavailable()
228 base_response = VCSServerUnavailable()
229
229
230 def is_http_error(response):
230 def is_http_error(response):
231 # error which should have traceback
231 # error which should have traceback
232 return response.status_code > 499
232 return response.status_code > 499
233
233
234 if is_http_error(base_response):
234 if is_http_error(base_response):
235 log.exception(
235 log.exception(
236 'error occurred handling this request for path: %s', request.path)
236 'error occurred handling this request for path: %s', request.path)
237
237
238 c = AttributeDict()
238 c = AttributeDict()
239 c.error_message = base_response.status
239 c.error_message = base_response.status
240 c.error_explanation = base_response.explanation or str(base_response)
240 c.error_explanation = base_response.explanation or str(base_response)
241 c.visual = AttributeDict()
241 c.visual = AttributeDict()
242
242
243 c.visual.rhodecode_support_url = (
243 c.visual.rhodecode_support_url = (
244 request.registry.settings.get('rhodecode_support_url') or
244 request.registry.settings.get('rhodecode_support_url') or
245 request.route_url('rhodecode_support')
245 request.route_url('rhodecode_support')
246 )
246 )
247 c.redirect_time = 0
247 c.redirect_time = 0
248 c.rhodecode_name = rhodecode_title
248 c.rhodecode_name = rhodecode_title
249 if not c.rhodecode_name:
249 if not c.rhodecode_name:
250 c.rhodecode_name = 'Rhodecode'
250 c.rhodecode_name = 'Rhodecode'
251
251
252 c.causes = []
252 c.causes = []
253 if hasattr(base_response, 'causes'):
253 if hasattr(base_response, 'causes'):
254 c.causes = base_response.causes
254 c.causes = base_response.causes
255 c.messages = helpers.flash.pop_messages(request=request)
255 c.messages = helpers.flash.pop_messages(request=request)
256 c.traceback = traceback.format_exc()
256 c.traceback = traceback.format_exc()
257 response = render_to_response(
257 response = render_to_response(
258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
259 response=base_response)
259 response=base_response)
260
260
261 return response
261 return response
262
262
263
263
264 def includeme(config):
264 def includeme(config):
265 settings = config.registry.settings
265 settings = config.registry.settings
266
266
267 # plugin information
267 # plugin information
268 config.registry.rhodecode_plugins = OrderedDict()
268 config.registry.rhodecode_plugins = OrderedDict()
269
269
270 config.add_directive(
270 config.add_directive(
271 'register_rhodecode_plugin', register_rhodecode_plugin)
271 'register_rhodecode_plugin', register_rhodecode_plugin)
272
272
273 if asbool(settings.get('appenlight', 'false')):
273 if asbool(settings.get('appenlight', 'false')):
274 config.include('appenlight_client.ext.pyramid_tween')
274 config.include('appenlight_client.ext.pyramid_tween')
275
275
276 if 'mako.default_filters' not in settings:
276 if 'mako.default_filters' not in settings:
277 # set custom default filters if we don't have it defined
277 # set custom default filters if we don't have it defined
278 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
278 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
279 settings['mako.default_filters'] = 'h_filter'
279 settings['mako.default_filters'] = 'h_filter'
280
280
281 # Includes which are required. The application would fail without them.
281 # Includes which are required. The application would fail without them.
282 config.include('pyramid_mako')
282 config.include('pyramid_mako')
283 config.include('pyramid_beaker')
283 config.include('pyramid_beaker')
284
284
285 config.include('rhodecode.authentication')
285 config.include('rhodecode.authentication')
286 config.include('rhodecode.integrations')
286 config.include('rhodecode.integrations')
287
287
288 # apps
288 # apps
289 config.include('rhodecode.apps._base')
289 config.include('rhodecode.apps._base')
290 config.include('rhodecode.apps.ops')
290 config.include('rhodecode.apps.ops')
291
291
292 config.include('rhodecode.apps.admin')
292 config.include('rhodecode.apps.admin')
293 config.include('rhodecode.apps.channelstream')
293 config.include('rhodecode.apps.channelstream')
294 config.include('rhodecode.apps.login')
294 config.include('rhodecode.apps.login')
295 config.include('rhodecode.apps.home')
295 config.include('rhodecode.apps.home')
296 config.include('rhodecode.apps.journal')
296 config.include('rhodecode.apps.journal')
297 config.include('rhodecode.apps.repository')
297 config.include('rhodecode.apps.repository')
298 config.include('rhodecode.apps.repo_group')
298 config.include('rhodecode.apps.repo_group')
299 config.include('rhodecode.apps.user_group')
299 config.include('rhodecode.apps.search')
300 config.include('rhodecode.apps.search')
300 config.include('rhodecode.apps.user_profile')
301 config.include('rhodecode.apps.user_profile')
301 config.include('rhodecode.apps.my_account')
302 config.include('rhodecode.apps.my_account')
302 config.include('rhodecode.apps.svn_support')
303 config.include('rhodecode.apps.svn_support')
303 config.include('rhodecode.apps.ssh_support')
304 config.include('rhodecode.apps.ssh_support')
304 config.include('rhodecode.apps.gist')
305 config.include('rhodecode.apps.gist')
305
306
306 config.include('rhodecode.apps.debug_style')
307 config.include('rhodecode.apps.debug_style')
307 config.include('rhodecode.tweens')
308 config.include('rhodecode.tweens')
308 config.include('rhodecode.api')
309 config.include('rhodecode.api')
309
310
310 config.add_route(
311 config.add_route(
311 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
312 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
312
313
313 config.add_translation_dirs('rhodecode:i18n/')
314 config.add_translation_dirs('rhodecode:i18n/')
314 settings['default_locale_name'] = settings.get('lang', 'en')
315 settings['default_locale_name'] = settings.get('lang', 'en')
315
316
316 # Add subscribers.
317 # Add subscribers.
317 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
318 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
318 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
319 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
319 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
320 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
320
321
321 config.add_request_method(
322 config.add_request_method(
322 'rhodecode.lib.partial_renderer.get_partial_renderer',
323 'rhodecode.lib.partial_renderer.get_partial_renderer',
323 'get_partial_renderer')
324 'get_partial_renderer')
324
325
325 # events
326 # events
326 # TODO(marcink): this should be done when pyramid migration is finished
327 # TODO(marcink): this should be done when pyramid migration is finished
327 # config.add_subscriber(
328 # config.add_subscriber(
328 # 'rhodecode.integrations.integrations_event_handler',
329 # 'rhodecode.integrations.integrations_event_handler',
329 # 'rhodecode.events.RhodecodeEvent')
330 # 'rhodecode.events.RhodecodeEvent')
330
331
331 # Set the authorization policy.
332 # Set the authorization policy.
332 authz_policy = ACLAuthorizationPolicy()
333 authz_policy = ACLAuthorizationPolicy()
333 config.set_authorization_policy(authz_policy)
334 config.set_authorization_policy(authz_policy)
334
335
335 # Set the default renderer for HTML templates to mako.
336 # Set the default renderer for HTML templates to mako.
336 config.add_mako_renderer('.html')
337 config.add_mako_renderer('.html')
337
338
338 config.add_renderer(
339 config.add_renderer(
339 name='json_ext',
340 name='json_ext',
340 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
341 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
341
342
342 # include RhodeCode plugins
343 # include RhodeCode plugins
343 includes = aslist(settings.get('rhodecode.includes', []))
344 includes = aslist(settings.get('rhodecode.includes', []))
344 for inc in includes:
345 for inc in includes:
345 config.include(inc)
346 config.include(inc)
346
347
347 # This is the glue which allows us to migrate in chunks. By registering the
348 # This is the glue which allows us to migrate in chunks. By registering the
348 # pylons based application as the "Not Found" view in Pyramid, we will
349 # pylons based application as the "Not Found" view in Pyramid, we will
349 # fallback to the old application each time the new one does not yet know
350 # fallback to the old application each time the new one does not yet know
350 # how to handle a request.
351 # how to handle a request.
351 config.add_notfound_view(make_not_found_view(config))
352 config.add_notfound_view(make_not_found_view(config))
352
353
353 if not settings.get('debugtoolbar.enabled', False):
354 if not settings.get('debugtoolbar.enabled', False):
354 # disabled debugtoolbar handle all exceptions via the error_handlers
355 # disabled debugtoolbar handle all exceptions via the error_handlers
355 config.add_view(error_handler, context=Exception)
356 config.add_view(error_handler, context=Exception)
356
357
357 config.add_view(error_handler, context=HTTPError)
358 config.add_view(error_handler, context=HTTPError)
358
359
359
360
360 def includeme_first(config):
361 def includeme_first(config):
361 # redirect automatic browser favicon.ico requests to correct place
362 # redirect automatic browser favicon.ico requests to correct place
362 def favicon_redirect(context, request):
363 def favicon_redirect(context, request):
363 return HTTPFound(
364 return HTTPFound(
364 request.static_path('rhodecode:public/images/favicon.ico'))
365 request.static_path('rhodecode:public/images/favicon.ico'))
365
366
366 config.add_view(favicon_redirect, route_name='favicon')
367 config.add_view(favicon_redirect, route_name='favicon')
367 config.add_route('favicon', '/favicon.ico')
368 config.add_route('favicon', '/favicon.ico')
368
369
369 def robots_redirect(context, request):
370 def robots_redirect(context, request):
370 return HTTPFound(
371 return HTTPFound(
371 request.static_path('rhodecode:public/robots.txt'))
372 request.static_path('rhodecode:public/robots.txt'))
372
373
373 config.add_view(robots_redirect, route_name='robots')
374 config.add_view(robots_redirect, route_name='robots')
374 config.add_route('robots', '/robots.txt')
375 config.add_route('robots', '/robots.txt')
375
376
376 config.add_static_view(
377 config.add_static_view(
377 '_static/deform', 'deform:static')
378 '_static/deform', 'deform:static')
378 config.add_static_view(
379 config.add_static_view(
379 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
380 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
380
381
381
382
382 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
383 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
383 """
384 """
384 Apply outer WSGI middlewares around the application.
385 Apply outer WSGI middlewares around the application.
385
386
386 Part of this has been moved up from the Pylons layer, so that the
387 Part of this has been moved up from the Pylons layer, so that the
387 data is also available if old Pylons code is hit through an already ported
388 data is also available if old Pylons code is hit through an already ported
388 view.
389 view.
389 """
390 """
390 settings = config.registry.settings
391 settings = config.registry.settings
391
392
392 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
393 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
393 pyramid_app = HttpsFixup(pyramid_app, settings)
394 pyramid_app = HttpsFixup(pyramid_app, settings)
394
395
395 # Add RoutesMiddleware to support the pylons compatibility tween during
396 # Add RoutesMiddleware to support the pylons compatibility tween during
396 # migration to pyramid.
397 # migration to pyramid.
397
398
398 # TODO(marcink): remove after migration to pyramid
399 # TODO(marcink): remove after migration to pyramid
399 if hasattr(config.registry, '_pylons_compat_config'):
400 if hasattr(config.registry, '_pylons_compat_config'):
400 routes_map = config.registry._pylons_compat_config['routes.map']
401 routes_map = config.registry._pylons_compat_config['routes.map']
401 pyramid_app = SkippableRoutesMiddleware(
402 pyramid_app = SkippableRoutesMiddleware(
402 pyramid_app, routes_map,
403 pyramid_app, routes_map,
403 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
404 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
404
405
405 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
406 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
406
407
407 if settings['gzip_responses']:
408 if settings['gzip_responses']:
408 pyramid_app = make_gzip_middleware(
409 pyramid_app = make_gzip_middleware(
409 pyramid_app, settings, compress_level=1)
410 pyramid_app, settings, compress_level=1)
410
411
411 # this should be the outer most middleware in the wsgi stack since
412 # this should be the outer most middleware in the wsgi stack since
412 # middleware like Routes make database calls
413 # middleware like Routes make database calls
413 def pyramid_app_with_cleanup(environ, start_response):
414 def pyramid_app_with_cleanup(environ, start_response):
414 try:
415 try:
415 return pyramid_app(environ, start_response)
416 return pyramid_app(environ, start_response)
416 finally:
417 finally:
417 # Dispose current database session and rollback uncommitted
418 # Dispose current database session and rollback uncommitted
418 # transactions.
419 # transactions.
419 meta.Session.remove()
420 meta.Session.remove()
420
421
421 # In a single threaded mode server, on non sqlite db we should have
422 # In a single threaded mode server, on non sqlite db we should have
422 # '0 Current Checked out connections' at the end of a request,
423 # '0 Current Checked out connections' at the end of a request,
423 # if not, then something, somewhere is leaving a connection open
424 # if not, then something, somewhere is leaving a connection open
424 pool = meta.Base.metadata.bind.engine.pool
425 pool = meta.Base.metadata.bind.engine.pool
425 log.debug('sa pool status: %s', pool.status())
426 log.debug('sa pool status: %s', pool.status())
426
427
427 return pyramid_app_with_cleanup
428 return pyramid_app_with_cleanup
428
429
429
430
430 def sanitize_settings_and_apply_defaults(settings):
431 def sanitize_settings_and_apply_defaults(settings):
431 """
432 """
432 Applies settings defaults and does all type conversion.
433 Applies settings defaults and does all type conversion.
433
434
434 We would move all settings parsing and preparation into this place, so that
435 We would move all settings parsing and preparation into this place, so that
435 we have only one place left which deals with this part. The remaining parts
436 we have only one place left which deals with this part. The remaining parts
436 of the application would start to rely fully on well prepared settings.
437 of the application would start to rely fully on well prepared settings.
437
438
438 This piece would later be split up per topic to avoid a big fat monster
439 This piece would later be split up per topic to avoid a big fat monster
439 function.
440 function.
440 """
441 """
441
442
442 # Pyramid's mako renderer has to search in the templates folder so that the
443 # Pyramid's mako renderer has to search in the templates folder so that the
443 # old templates still work. Ported and new templates are expected to use
444 # old templates still work. Ported and new templates are expected to use
444 # real asset specifications for the includes.
445 # real asset specifications for the includes.
445 mako_directories = settings.setdefault('mako.directories', [
446 mako_directories = settings.setdefault('mako.directories', [
446 # Base templates of the original Pylons application
447 # Base templates of the original Pylons application
447 'rhodecode:templates',
448 'rhodecode:templates',
448 ])
449 ])
449 log.debug(
450 log.debug(
450 "Using the following Mako template directories: %s",
451 "Using the following Mako template directories: %s",
451 mako_directories)
452 mako_directories)
452
453
453 # Default includes, possible to change as a user
454 # Default includes, possible to change as a user
454 pyramid_includes = settings.setdefault('pyramid.includes', [
455 pyramid_includes = settings.setdefault('pyramid.includes', [
455 'rhodecode.lib.middleware.request_wrapper',
456 'rhodecode.lib.middleware.request_wrapper',
456 ])
457 ])
457 log.debug(
458 log.debug(
458 "Using the following pyramid.includes: %s",
459 "Using the following pyramid.includes: %s",
459 pyramid_includes)
460 pyramid_includes)
460
461
461 # TODO: johbo: Re-think this, usually the call to config.include
462 # TODO: johbo: Re-think this, usually the call to config.include
462 # should allow to pass in a prefix.
463 # should allow to pass in a prefix.
463 settings.setdefault('rhodecode.api.url', '/_admin/api')
464 settings.setdefault('rhodecode.api.url', '/_admin/api')
464
465
465 # Sanitize generic settings.
466 # Sanitize generic settings.
466 _list_setting(settings, 'default_encoding', 'UTF-8')
467 _list_setting(settings, 'default_encoding', 'UTF-8')
467 _bool_setting(settings, 'is_test', 'false')
468 _bool_setting(settings, 'is_test', 'false')
468 _bool_setting(settings, 'gzip_responses', 'false')
469 _bool_setting(settings, 'gzip_responses', 'false')
469
470
470 # Call split out functions that sanitize settings for each topic.
471 # Call split out functions that sanitize settings for each topic.
471 _sanitize_appenlight_settings(settings)
472 _sanitize_appenlight_settings(settings)
472 _sanitize_vcs_settings(settings)
473 _sanitize_vcs_settings(settings)
473
474
474 return settings
475 return settings
475
476
476
477
477 def _sanitize_appenlight_settings(settings):
478 def _sanitize_appenlight_settings(settings):
478 _bool_setting(settings, 'appenlight', 'false')
479 _bool_setting(settings, 'appenlight', 'false')
479
480
480
481
481 def _sanitize_vcs_settings(settings):
482 def _sanitize_vcs_settings(settings):
482 """
483 """
483 Applies settings defaults and does type conversion for all VCS related
484 Applies settings defaults and does type conversion for all VCS related
484 settings.
485 settings.
485 """
486 """
486 _string_setting(settings, 'vcs.svn.compatible_version', '')
487 _string_setting(settings, 'vcs.svn.compatible_version', '')
487 _string_setting(settings, 'git_rev_filter', '--all')
488 _string_setting(settings, 'git_rev_filter', '--all')
488 _string_setting(settings, 'vcs.hooks.protocol', 'http')
489 _string_setting(settings, 'vcs.hooks.protocol', 'http')
489 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
490 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
490 _string_setting(settings, 'vcs.server', '')
491 _string_setting(settings, 'vcs.server', '')
491 _string_setting(settings, 'vcs.server.log_level', 'debug')
492 _string_setting(settings, 'vcs.server.log_level', 'debug')
492 _string_setting(settings, 'vcs.server.protocol', 'http')
493 _string_setting(settings, 'vcs.server.protocol', 'http')
493 _bool_setting(settings, 'startup.import_repos', 'false')
494 _bool_setting(settings, 'startup.import_repos', 'false')
494 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
495 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
495 _bool_setting(settings, 'vcs.server.enable', 'true')
496 _bool_setting(settings, 'vcs.server.enable', 'true')
496 _bool_setting(settings, 'vcs.start_server', 'false')
497 _bool_setting(settings, 'vcs.start_server', 'false')
497 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
498 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
498 _int_setting(settings, 'vcs.connection_timeout', 3600)
499 _int_setting(settings, 'vcs.connection_timeout', 3600)
499
500
500 # Support legacy values of vcs.scm_app_implementation. Legacy
501 # Support legacy values of vcs.scm_app_implementation. Legacy
501 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
502 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
502 # which is now mapped to 'http'.
503 # which is now mapped to 'http'.
503 scm_app_impl = settings['vcs.scm_app_implementation']
504 scm_app_impl = settings['vcs.scm_app_implementation']
504 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
505 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
505 settings['vcs.scm_app_implementation'] = 'http'
506 settings['vcs.scm_app_implementation'] = 'http'
506
507
507
508
508 def _int_setting(settings, name, default):
509 def _int_setting(settings, name, default):
509 settings[name] = int(settings.get(name, default))
510 settings[name] = int(settings.get(name, default))
510
511
511
512
512 def _bool_setting(settings, name, default):
513 def _bool_setting(settings, name, default):
513 input = settings.get(name, default)
514 input = settings.get(name, default)
514 if isinstance(input, unicode):
515 if isinstance(input, unicode):
515 input = input.encode('utf8')
516 input = input.encode('utf8')
516 settings[name] = asbool(input)
517 settings[name] = asbool(input)
517
518
518
519
519 def _list_setting(settings, name, default):
520 def _list_setting(settings, name, default):
520 raw_value = settings.get(name, default)
521 raw_value = settings.get(name, default)
521
522
522 old_separator = ','
523 old_separator = ','
523 if old_separator in raw_value:
524 if old_separator in raw_value:
524 # If we get a comma separated list, pass it to our own function.
525 # If we get a comma separated list, pass it to our own function.
525 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
526 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
526 else:
527 else:
527 # Otherwise we assume it uses pyramids space/newline separation.
528 # Otherwise we assume it uses pyramids space/newline separation.
528 settings[name] = aslist(raw_value)
529 settings[name] = aslist(raw_value)
529
530
530
531
531 def _string_setting(settings, name, default, lower=True):
532 def _string_setting(settings, name, default, lower=True):
532 value = settings.get(name, default)
533 value = settings.get(name, default)
533 if lower:
534 if lower:
534 value = value.lower()
535 value = value.lower()
535 settings[name] = value
536 settings[name] = value
@@ -1,393 +1,355 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY GROUPS ROUTES
179 # ADMIN REPOSITORY GROUPS ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repo_groups') as m:
181 controller='admin/repo_groups') as m:
182 m.connect('repo_groups', '/repo_groups',
182 m.connect('repo_groups', '/repo_groups',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repo_groups', '/repo_groups',
184 m.connect('repo_groups', '/repo_groups',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo_group', '/repo_groups/new',
186 m.connect('new_repo_group', '/repo_groups/new',
187 action='new', conditions={'method': ['GET']})
187 action='new', conditions={'method': ['GET']})
188 m.connect('update_repo_group', '/repo_groups/{group_name}',
188 m.connect('update_repo_group', '/repo_groups/{group_name}',
189 action='update', conditions={'method': ['PUT'],
189 action='update', conditions={'method': ['PUT'],
190 'function': check_group},
190 'function': check_group},
191 requirements=URL_NAME_REQUIREMENTS)
191 requirements=URL_NAME_REQUIREMENTS)
192
192
193 # EXTRAS REPO GROUP ROUTES
193 # EXTRAS REPO GROUP ROUTES
194 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
194 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
195 action='edit',
195 action='edit',
196 conditions={'method': ['GET'], 'function': check_group},
196 conditions={'method': ['GET'], 'function': check_group},
197 requirements=URL_NAME_REQUIREMENTS)
197 requirements=URL_NAME_REQUIREMENTS)
198 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
198 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
199 action='edit',
199 action='edit',
200 conditions={'method': ['PUT'], 'function': check_group},
200 conditions={'method': ['PUT'], 'function': check_group},
201 requirements=URL_NAME_REQUIREMENTS)
201 requirements=URL_NAME_REQUIREMENTS)
202
202
203 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
203 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
204 action='edit_repo_group_advanced',
204 action='edit_repo_group_advanced',
205 conditions={'method': ['GET'], 'function': check_group},
205 conditions={'method': ['GET'], 'function': check_group},
206 requirements=URL_NAME_REQUIREMENTS)
206 requirements=URL_NAME_REQUIREMENTS)
207 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
207 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
208 action='edit_repo_group_advanced',
208 action='edit_repo_group_advanced',
209 conditions={'method': ['PUT'], 'function': check_group},
209 conditions={'method': ['PUT'], 'function': check_group},
210 requirements=URL_NAME_REQUIREMENTS)
210 requirements=URL_NAME_REQUIREMENTS)
211
211
212 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
212 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
213 action='edit_repo_group_perms',
213 action='edit_repo_group_perms',
214 conditions={'method': ['GET'], 'function': check_group},
214 conditions={'method': ['GET'], 'function': check_group},
215 requirements=URL_NAME_REQUIREMENTS)
215 requirements=URL_NAME_REQUIREMENTS)
216 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
216 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
217 action='update_perms',
217 action='update_perms',
218 conditions={'method': ['PUT'], 'function': check_group},
218 conditions={'method': ['PUT'], 'function': check_group},
219 requirements=URL_NAME_REQUIREMENTS)
219 requirements=URL_NAME_REQUIREMENTS)
220
220
221 m.connect('delete_repo_group', '/repo_groups/{group_name}',
221 m.connect('delete_repo_group', '/repo_groups/{group_name}',
222 action='delete', conditions={'method': ['DELETE'],
222 action='delete', conditions={'method': ['DELETE'],
223 'function': check_group},
223 'function': check_group},
224 requirements=URL_NAME_REQUIREMENTS)
224 requirements=URL_NAME_REQUIREMENTS)
225
225
226 # ADMIN USER ROUTES
226 # ADMIN USER ROUTES
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 controller='admin/users') as m:
228 controller='admin/users') as m:
229 m.connect('users', '/users',
229 m.connect('users', '/users',
230 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
231 m.connect('new_user', '/users/new',
231 m.connect('new_user', '/users/new',
232 action='new', conditions={'method': ['GET']})
232 action='new', conditions={'method': ['GET']})
233 m.connect('update_user', '/users/{user_id}',
233 m.connect('update_user', '/users/{user_id}',
234 action='update', conditions={'method': ['PUT']})
234 action='update', conditions={'method': ['PUT']})
235 m.connect('delete_user', '/users/{user_id}',
235 m.connect('delete_user', '/users/{user_id}',
236 action='delete', conditions={'method': ['DELETE']})
236 action='delete', conditions={'method': ['DELETE']})
237 m.connect('edit_user', '/users/{user_id}/edit',
237 m.connect('edit_user', '/users/{user_id}/edit',
238 action='edit', conditions={'method': ['GET']}, jsroute=True)
238 action='edit', conditions={'method': ['GET']}, jsroute=True)
239 m.connect('user', '/users/{user_id}',
239 m.connect('user', '/users/{user_id}',
240 action='show', conditions={'method': ['GET']})
240 action='show', conditions={'method': ['GET']})
241 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
241 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
242 action='reset_password', conditions={'method': ['POST']})
242 action='reset_password', conditions={'method': ['POST']})
243 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
243 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
244 action='create_personal_repo_group', conditions={'method': ['POST']})
244 action='create_personal_repo_group', conditions={'method': ['POST']})
245
245
246 # EXTRAS USER ROUTES
246 # EXTRAS USER ROUTES
247 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
247 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
248 action='edit_advanced', conditions={'method': ['GET']})
248 action='edit_advanced', conditions={'method': ['GET']})
249 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
249 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
250 action='update_advanced', conditions={'method': ['PUT']})
250 action='update_advanced', conditions={'method': ['PUT']})
251
251
252 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
252 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
253 action='edit_global_perms', conditions={'method': ['GET']})
253 action='edit_global_perms', conditions={'method': ['GET']})
254 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
254 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
255 action='update_global_perms', conditions={'method': ['PUT']})
255 action='update_global_perms', conditions={'method': ['PUT']})
256
256
257 # ADMIN USER GROUPS REST ROUTES
258 with rmap.submapper(path_prefix=ADMIN_PREFIX,
259 controller='admin/user_groups') as m:
260 m.connect('users_groups', '/user_groups',
261 action='create', conditions={'method': ['POST']})
262 m.connect('new_users_group', '/user_groups/new',
263 action='new', conditions={'method': ['GET']})
264 m.connect('update_users_group', '/user_groups/{user_group_id}',
265 action='update', conditions={'method': ['PUT']})
266 m.connect('delete_users_group', '/user_groups/{user_group_id}',
267 action='delete', conditions={'method': ['DELETE']})
268 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
269 action='edit', conditions={'method': ['GET']},
270 function=check_user_group)
271
272 # EXTRAS USER GROUP ROUTES
273 m.connect('edit_user_group_global_perms',
274 '/user_groups/{user_group_id}/edit/global_permissions',
275 action='edit_global_perms', conditions={'method': ['GET']})
276 m.connect('edit_user_group_global_perms',
277 '/user_groups/{user_group_id}/edit/global_permissions',
278 action='update_global_perms', conditions={'method': ['PUT']})
279
280 m.connect('edit_user_group_perms',
281 '/user_groups/{user_group_id}/edit/permissions',
282 action='edit_perms', conditions={'method': ['GET']})
283 m.connect('edit_user_group_perms',
284 '/user_groups/{user_group_id}/edit/permissions',
285 action='update_perms', conditions={'method': ['PUT']})
286
287 m.connect('edit_user_group_advanced',
288 '/user_groups/{user_group_id}/edit/advanced',
289 action='edit_advanced', conditions={'method': ['GET']})
290
291 m.connect('edit_user_group_advanced_sync',
292 '/user_groups/{user_group_id}/edit/advanced/sync',
293 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
294
295 # ADMIN DEFAULTS REST ROUTES
257 # ADMIN DEFAULTS REST ROUTES
296 with rmap.submapper(path_prefix=ADMIN_PREFIX,
258 with rmap.submapper(path_prefix=ADMIN_PREFIX,
297 controller='admin/defaults') as m:
259 controller='admin/defaults') as m:
298 m.connect('admin_defaults_repositories', '/defaults/repositories',
260 m.connect('admin_defaults_repositories', '/defaults/repositories',
299 action='update_repository_defaults', conditions={'method': ['POST']})
261 action='update_repository_defaults', conditions={'method': ['POST']})
300 m.connect('admin_defaults_repositories', '/defaults/repositories',
262 m.connect('admin_defaults_repositories', '/defaults/repositories',
301 action='index', conditions={'method': ['GET']})
263 action='index', conditions={'method': ['GET']})
302
264
303 # ADMIN SETTINGS ROUTES
265 # ADMIN SETTINGS ROUTES
304 with rmap.submapper(path_prefix=ADMIN_PREFIX,
266 with rmap.submapper(path_prefix=ADMIN_PREFIX,
305 controller='admin/settings') as m:
267 controller='admin/settings') as m:
306
268
307 # default
269 # default
308 m.connect('admin_settings', '/settings',
270 m.connect('admin_settings', '/settings',
309 action='settings_global_update',
271 action='settings_global_update',
310 conditions={'method': ['POST']})
272 conditions={'method': ['POST']})
311 m.connect('admin_settings', '/settings',
273 m.connect('admin_settings', '/settings',
312 action='settings_global', conditions={'method': ['GET']})
274 action='settings_global', conditions={'method': ['GET']})
313
275
314 m.connect('admin_settings_vcs', '/settings/vcs',
276 m.connect('admin_settings_vcs', '/settings/vcs',
315 action='settings_vcs_update',
277 action='settings_vcs_update',
316 conditions={'method': ['POST']})
278 conditions={'method': ['POST']})
317 m.connect('admin_settings_vcs', '/settings/vcs',
279 m.connect('admin_settings_vcs', '/settings/vcs',
318 action='settings_vcs',
280 action='settings_vcs',
319 conditions={'method': ['GET']})
281 conditions={'method': ['GET']})
320 m.connect('admin_settings_vcs', '/settings/vcs',
282 m.connect('admin_settings_vcs', '/settings/vcs',
321 action='delete_svn_pattern',
283 action='delete_svn_pattern',
322 conditions={'method': ['DELETE']})
284 conditions={'method': ['DELETE']})
323
285
324 m.connect('admin_settings_mapping', '/settings/mapping',
286 m.connect('admin_settings_mapping', '/settings/mapping',
325 action='settings_mapping_update',
287 action='settings_mapping_update',
326 conditions={'method': ['POST']})
288 conditions={'method': ['POST']})
327 m.connect('admin_settings_mapping', '/settings/mapping',
289 m.connect('admin_settings_mapping', '/settings/mapping',
328 action='settings_mapping', conditions={'method': ['GET']})
290 action='settings_mapping', conditions={'method': ['GET']})
329
291
330 m.connect('admin_settings_global', '/settings/global',
292 m.connect('admin_settings_global', '/settings/global',
331 action='settings_global_update',
293 action='settings_global_update',
332 conditions={'method': ['POST']})
294 conditions={'method': ['POST']})
333 m.connect('admin_settings_global', '/settings/global',
295 m.connect('admin_settings_global', '/settings/global',
334 action='settings_global', conditions={'method': ['GET']})
296 action='settings_global', conditions={'method': ['GET']})
335
297
336 m.connect('admin_settings_visual', '/settings/visual',
298 m.connect('admin_settings_visual', '/settings/visual',
337 action='settings_visual_update',
299 action='settings_visual_update',
338 conditions={'method': ['POST']})
300 conditions={'method': ['POST']})
339 m.connect('admin_settings_visual', '/settings/visual',
301 m.connect('admin_settings_visual', '/settings/visual',
340 action='settings_visual', conditions={'method': ['GET']})
302 action='settings_visual', conditions={'method': ['GET']})
341
303
342 m.connect('admin_settings_issuetracker',
304 m.connect('admin_settings_issuetracker',
343 '/settings/issue-tracker', action='settings_issuetracker',
305 '/settings/issue-tracker', action='settings_issuetracker',
344 conditions={'method': ['GET']})
306 conditions={'method': ['GET']})
345 m.connect('admin_settings_issuetracker_save',
307 m.connect('admin_settings_issuetracker_save',
346 '/settings/issue-tracker/save',
308 '/settings/issue-tracker/save',
347 action='settings_issuetracker_save',
309 action='settings_issuetracker_save',
348 conditions={'method': ['POST']})
310 conditions={'method': ['POST']})
349 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
311 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
350 action='settings_issuetracker_test',
312 action='settings_issuetracker_test',
351 conditions={'method': ['POST']})
313 conditions={'method': ['POST']})
352 m.connect('admin_issuetracker_delete',
314 m.connect('admin_issuetracker_delete',
353 '/settings/issue-tracker/delete',
315 '/settings/issue-tracker/delete',
354 action='settings_issuetracker_delete',
316 action='settings_issuetracker_delete',
355 conditions={'method': ['DELETE']})
317 conditions={'method': ['DELETE']})
356
318
357 m.connect('admin_settings_email', '/settings/email',
319 m.connect('admin_settings_email', '/settings/email',
358 action='settings_email_update',
320 action='settings_email_update',
359 conditions={'method': ['POST']})
321 conditions={'method': ['POST']})
360 m.connect('admin_settings_email', '/settings/email',
322 m.connect('admin_settings_email', '/settings/email',
361 action='settings_email', conditions={'method': ['GET']})
323 action='settings_email', conditions={'method': ['GET']})
362
324
363 m.connect('admin_settings_hooks', '/settings/hooks',
325 m.connect('admin_settings_hooks', '/settings/hooks',
364 action='settings_hooks_update',
326 action='settings_hooks_update',
365 conditions={'method': ['POST', 'DELETE']})
327 conditions={'method': ['POST', 'DELETE']})
366 m.connect('admin_settings_hooks', '/settings/hooks',
328 m.connect('admin_settings_hooks', '/settings/hooks',
367 action='settings_hooks', conditions={'method': ['GET']})
329 action='settings_hooks', conditions={'method': ['GET']})
368
330
369 m.connect('admin_settings_search', '/settings/search',
331 m.connect('admin_settings_search', '/settings/search',
370 action='settings_search', conditions={'method': ['GET']})
332 action='settings_search', conditions={'method': ['GET']})
371
333
372 m.connect('admin_settings_supervisor', '/settings/supervisor',
334 m.connect('admin_settings_supervisor', '/settings/supervisor',
373 action='settings_supervisor', conditions={'method': ['GET']})
335 action='settings_supervisor', conditions={'method': ['GET']})
374 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
336 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
375 action='settings_supervisor_log', conditions={'method': ['GET']})
337 action='settings_supervisor_log', conditions={'method': ['GET']})
376
338
377 m.connect('admin_settings_labs', '/settings/labs',
339 m.connect('admin_settings_labs', '/settings/labs',
378 action='settings_labs_update',
340 action='settings_labs_update',
379 conditions={'method': ['POST']})
341 conditions={'method': ['POST']})
380 m.connect('admin_settings_labs', '/settings/labs',
342 m.connect('admin_settings_labs', '/settings/labs',
381 action='settings_labs', conditions={'method': ['GET']})
343 action='settings_labs', conditions={'method': ['GET']})
382
344
383 # ADMIN MY ACCOUNT
345 # ADMIN MY ACCOUNT
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
346 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 controller='admin/my_account') as m:
347 controller='admin/my_account') as m:
386
348
387 # NOTE(marcink): this needs to be kept for password force flag to be
349 # NOTE(marcink): this needs to be kept for password force flag to be
388 # handled in pylons controllers, remove after full migration to pyramid
350 # handled in pylons controllers, remove after full migration to pyramid
389 m.connect('my_account_password', '/my_account/password',
351 m.connect('my_account_password', '/my_account/password',
390 action='my_account_password', conditions={'method': ['GET']})
352 action='my_account_password', conditions={'method': ['GET']})
391
353
392
354
393 return rmap
355 return rmap
@@ -1,2161 +1,2161 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import inspect
26 import inspect
27 import collections
27 import collections
28 import fnmatch
28 import fnmatch
29 import hashlib
29 import hashlib
30 import itertools
30 import itertools
31 import logging
31 import logging
32 import random
32 import random
33 import traceback
33 import traceback
34 from functools import wraps
34 from functools import wraps
35
35
36 import ipaddress
36 import ipaddress
37 from beaker.cache import cache_region
37 from beaker.cache import cache_region
38 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
38 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pylons.i18n.translation import _
39 from pylons.i18n.translation import _
40 # NOTE(marcink): this has to be removed only after pyramid migration,
40 # NOTE(marcink): this has to be removed only after pyramid migration,
41 # replace with _ = request.translate
41 # replace with _ = request.translate
42 from sqlalchemy.orm.exc import ObjectDeletedError
42 from sqlalchemy.orm.exc import ObjectDeletedError
43 from sqlalchemy.orm import joinedload
43 from sqlalchemy.orm import joinedload
44 from zope.cachedescriptors.property import Lazy as LazyProperty
44 from zope.cachedescriptors.property import Lazy as LazyProperty
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.db import (
50 from rhodecode.model.db import (
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
52 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
52 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
53 from rhodecode.lib import caches
53 from rhodecode.lib import caches
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
55 from rhodecode.lib.utils import (
55 from rhodecode.lib.utils import (
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
57 from rhodecode.lib.caching_query import FromCache
57 from rhodecode.lib.caching_query import FromCache
58
58
59
59
60 if rhodecode.is_unix:
60 if rhodecode.is_unix:
61 import bcrypt
61 import bcrypt
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65 csrf_token_key = "csrf_token"
65 csrf_token_key = "csrf_token"
66
66
67
67
68 class PasswordGenerator(object):
68 class PasswordGenerator(object):
69 """
69 """
70 This is a simple class for generating password from different sets of
70 This is a simple class for generating password from different sets of
71 characters
71 characters
72 usage::
72 usage::
73
73
74 passwd_gen = PasswordGenerator()
74 passwd_gen = PasswordGenerator()
75 #print 8-letter password containing only big and small letters
75 #print 8-letter password containing only big and small letters
76 of alphabet
76 of alphabet
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
78 """
78 """
79 ALPHABETS_NUM = r'''1234567890'''
79 ALPHABETS_NUM = r'''1234567890'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
89
89
90 def __init__(self, passwd=''):
90 def __init__(self, passwd=''):
91 self.passwd = passwd
91 self.passwd = passwd
92
92
93 def gen_password(self, length, type_=None):
93 def gen_password(self, length, type_=None):
94 if type_ is None:
94 if type_ is None:
95 type_ = self.ALPHABETS_FULL
95 type_ = self.ALPHABETS_FULL
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
97 return self.passwd
97 return self.passwd
98
98
99
99
100 class _RhodeCodeCryptoBase(object):
100 class _RhodeCodeCryptoBase(object):
101 ENC_PREF = None
101 ENC_PREF = None
102
102
103 def hash_create(self, str_):
103 def hash_create(self, str_):
104 """
104 """
105 hash the string using
105 hash the string using
106
106
107 :param str_: password to hash
107 :param str_: password to hash
108 """
108 """
109 raise NotImplementedError
109 raise NotImplementedError
110
110
111 def hash_check_with_upgrade(self, password, hashed):
111 def hash_check_with_upgrade(self, password, hashed):
112 """
112 """
113 Returns tuple in which first element is boolean that states that
113 Returns tuple in which first element is boolean that states that
114 given password matches it's hashed version, and the second is new hash
114 given password matches it's hashed version, and the second is new hash
115 of the password, in case this password should be migrated to new
115 of the password, in case this password should be migrated to new
116 cipher.
116 cipher.
117 """
117 """
118 checked_hash = self.hash_check(password, hashed)
118 checked_hash = self.hash_check(password, hashed)
119 return checked_hash, None
119 return checked_hash, None
120
120
121 def hash_check(self, password, hashed):
121 def hash_check(self, password, hashed):
122 """
122 """
123 Checks matching password with it's hashed value.
123 Checks matching password with it's hashed value.
124
124
125 :param password: password
125 :param password: password
126 :param hashed: password in hashed form
126 :param hashed: password in hashed form
127 """
127 """
128 raise NotImplementedError
128 raise NotImplementedError
129
129
130 def _assert_bytes(self, value):
130 def _assert_bytes(self, value):
131 """
131 """
132 Passing in an `unicode` object can lead to hard to detect issues
132 Passing in an `unicode` object can lead to hard to detect issues
133 if passwords contain non-ascii characters. Doing a type check
133 if passwords contain non-ascii characters. Doing a type check
134 during runtime, so that such mistakes are detected early on.
134 during runtime, so that such mistakes are detected early on.
135 """
135 """
136 if not isinstance(value, str):
136 if not isinstance(value, str):
137 raise TypeError(
137 raise TypeError(
138 "Bytestring required as input, got %r." % (value, ))
138 "Bytestring required as input, got %r." % (value, ))
139
139
140
140
141 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
142 ENC_PREF = ('$2a$10', '$2b$10')
142 ENC_PREF = ('$2a$10', '$2b$10')
143
143
144 def hash_create(self, str_):
144 def hash_create(self, str_):
145 self._assert_bytes(str_)
145 self._assert_bytes(str_)
146 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
146 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
147
147
148 def hash_check_with_upgrade(self, password, hashed):
148 def hash_check_with_upgrade(self, password, hashed):
149 """
149 """
150 Returns tuple in which first element is boolean that states that
150 Returns tuple in which first element is boolean that states that
151 given password matches it's hashed version, and the second is new hash
151 given password matches it's hashed version, and the second is new hash
152 of the password, in case this password should be migrated to new
152 of the password, in case this password should be migrated to new
153 cipher.
153 cipher.
154
154
155 This implements special upgrade logic which works like that:
155 This implements special upgrade logic which works like that:
156 - check if the given password == bcrypted hash, if yes then we
156 - check if the given password == bcrypted hash, if yes then we
157 properly used password and it was already in bcrypt. Proceed
157 properly used password and it was already in bcrypt. Proceed
158 without any changes
158 without any changes
159 - if bcrypt hash check is not working try with sha256. If hash compare
159 - if bcrypt hash check is not working try with sha256. If hash compare
160 is ok, it means we using correct but old hashed password. indicate
160 is ok, it means we using correct but old hashed password. indicate
161 hash change and proceed
161 hash change and proceed
162 """
162 """
163
163
164 new_hash = None
164 new_hash = None
165
165
166 # regular pw check
166 # regular pw check
167 password_match_bcrypt = self.hash_check(password, hashed)
167 password_match_bcrypt = self.hash_check(password, hashed)
168
168
169 # now we want to know if the password was maybe from sha256
169 # now we want to know if the password was maybe from sha256
170 # basically calling _RhodeCodeCryptoSha256().hash_check()
170 # basically calling _RhodeCodeCryptoSha256().hash_check()
171 if not password_match_bcrypt:
171 if not password_match_bcrypt:
172 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
172 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
173 new_hash = self.hash_create(password) # make new bcrypt hash
173 new_hash = self.hash_create(password) # make new bcrypt hash
174 password_match_bcrypt = True
174 password_match_bcrypt = True
175
175
176 return password_match_bcrypt, new_hash
176 return password_match_bcrypt, new_hash
177
177
178 def hash_check(self, password, hashed):
178 def hash_check(self, password, hashed):
179 """
179 """
180 Checks matching password with it's hashed value.
180 Checks matching password with it's hashed value.
181
181
182 :param password: password
182 :param password: password
183 :param hashed: password in hashed form
183 :param hashed: password in hashed form
184 """
184 """
185 self._assert_bytes(password)
185 self._assert_bytes(password)
186 try:
186 try:
187 return bcrypt.hashpw(password, hashed) == hashed
187 return bcrypt.hashpw(password, hashed) == hashed
188 except ValueError as e:
188 except ValueError as e:
189 # we're having a invalid salt here probably, we should not crash
189 # we're having a invalid salt here probably, we should not crash
190 # just return with False as it would be a wrong password.
190 # just return with False as it would be a wrong password.
191 log.debug('Failed to check password hash using bcrypt %s',
191 log.debug('Failed to check password hash using bcrypt %s',
192 safe_str(e))
192 safe_str(e))
193
193
194 return False
194 return False
195
195
196
196
197 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
197 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
198 ENC_PREF = '_'
198 ENC_PREF = '_'
199
199
200 def hash_create(self, str_):
200 def hash_create(self, str_):
201 self._assert_bytes(str_)
201 self._assert_bytes(str_)
202 return hashlib.sha256(str_).hexdigest()
202 return hashlib.sha256(str_).hexdigest()
203
203
204 def hash_check(self, password, hashed):
204 def hash_check(self, password, hashed):
205 """
205 """
206 Checks matching password with it's hashed value.
206 Checks matching password with it's hashed value.
207
207
208 :param password: password
208 :param password: password
209 :param hashed: password in hashed form
209 :param hashed: password in hashed form
210 """
210 """
211 self._assert_bytes(password)
211 self._assert_bytes(password)
212 return hashlib.sha256(password).hexdigest() == hashed
212 return hashlib.sha256(password).hexdigest() == hashed
213
213
214
214
215 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
215 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
216 ENC_PREF = '_'
216 ENC_PREF = '_'
217
217
218 def hash_create(self, str_):
218 def hash_create(self, str_):
219 self._assert_bytes(str_)
219 self._assert_bytes(str_)
220 return hashlib.md5(str_).hexdigest()
220 return hashlib.md5(str_).hexdigest()
221
221
222 def hash_check(self, password, hashed):
222 def hash_check(self, password, hashed):
223 """
223 """
224 Checks matching password with it's hashed value.
224 Checks matching password with it's hashed value.
225
225
226 :param password: password
226 :param password: password
227 :param hashed: password in hashed form
227 :param hashed: password in hashed form
228 """
228 """
229 self._assert_bytes(password)
229 self._assert_bytes(password)
230 return hashlib.md5(password).hexdigest() == hashed
230 return hashlib.md5(password).hexdigest() == hashed
231
231
232
232
233 def crypto_backend():
233 def crypto_backend():
234 """
234 """
235 Return the matching crypto backend.
235 Return the matching crypto backend.
236
236
237 Selection is based on if we run tests or not, we pick md5 backend to run
237 Selection is based on if we run tests or not, we pick md5 backend to run
238 tests faster since BCRYPT is expensive to calculate
238 tests faster since BCRYPT is expensive to calculate
239 """
239 """
240 if rhodecode.is_test:
240 if rhodecode.is_test:
241 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
241 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
242 else:
242 else:
243 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
243 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
244
244
245 return RhodeCodeCrypto
245 return RhodeCodeCrypto
246
246
247
247
248 def get_crypt_password(password):
248 def get_crypt_password(password):
249 """
249 """
250 Create the hash of `password` with the active crypto backend.
250 Create the hash of `password` with the active crypto backend.
251
251
252 :param password: The cleartext password.
252 :param password: The cleartext password.
253 :type password: unicode
253 :type password: unicode
254 """
254 """
255 password = safe_str(password)
255 password = safe_str(password)
256 return crypto_backend().hash_create(password)
256 return crypto_backend().hash_create(password)
257
257
258
258
259 def check_password(password, hashed):
259 def check_password(password, hashed):
260 """
260 """
261 Check if the value in `password` matches the hash in `hashed`.
261 Check if the value in `password` matches the hash in `hashed`.
262
262
263 :param password: The cleartext password.
263 :param password: The cleartext password.
264 :type password: unicode
264 :type password: unicode
265
265
266 :param hashed: The expected hashed version of the password.
266 :param hashed: The expected hashed version of the password.
267 :type hashed: The hash has to be passed in in text representation.
267 :type hashed: The hash has to be passed in in text representation.
268 """
268 """
269 password = safe_str(password)
269 password = safe_str(password)
270 return crypto_backend().hash_check(password, hashed)
270 return crypto_backend().hash_check(password, hashed)
271
271
272
272
273 def generate_auth_token(data, salt=None):
273 def generate_auth_token(data, salt=None):
274 """
274 """
275 Generates API KEY from given string
275 Generates API KEY from given string
276 """
276 """
277
277
278 if salt is None:
278 if salt is None:
279 salt = os.urandom(16)
279 salt = os.urandom(16)
280 return hashlib.sha1(safe_str(data) + salt).hexdigest()
280 return hashlib.sha1(safe_str(data) + salt).hexdigest()
281
281
282
282
283 class CookieStoreWrapper(object):
283 class CookieStoreWrapper(object):
284
284
285 def __init__(self, cookie_store):
285 def __init__(self, cookie_store):
286 self.cookie_store = cookie_store
286 self.cookie_store = cookie_store
287
287
288 def __repr__(self):
288 def __repr__(self):
289 return 'CookieStore<%s>' % (self.cookie_store)
289 return 'CookieStore<%s>' % (self.cookie_store)
290
290
291 def get(self, key, other=None):
291 def get(self, key, other=None):
292 if isinstance(self.cookie_store, dict):
292 if isinstance(self.cookie_store, dict):
293 return self.cookie_store.get(key, other)
293 return self.cookie_store.get(key, other)
294 elif isinstance(self.cookie_store, AuthUser):
294 elif isinstance(self.cookie_store, AuthUser):
295 return self.cookie_store.__dict__.get(key, other)
295 return self.cookie_store.__dict__.get(key, other)
296
296
297
297
298 def _cached_perms_data(user_id, scope, user_is_admin,
298 def _cached_perms_data(user_id, scope, user_is_admin,
299 user_inherit_default_permissions, explicit, algo,
299 user_inherit_default_permissions, explicit, algo,
300 calculate_super_admin):
300 calculate_super_admin):
301
301
302 permissions = PermissionCalculator(
302 permissions = PermissionCalculator(
303 user_id, scope, user_is_admin, user_inherit_default_permissions,
303 user_id, scope, user_is_admin, user_inherit_default_permissions,
304 explicit, algo, calculate_super_admin)
304 explicit, algo, calculate_super_admin)
305 return permissions.calculate()
305 return permissions.calculate()
306
306
307
307
308 class PermOrigin(object):
308 class PermOrigin(object):
309 SUPER_ADMIN = 'superadmin'
309 SUPER_ADMIN = 'superadmin'
310
310
311 REPO_USER = 'user:%s'
311 REPO_USER = 'user:%s'
312 REPO_USERGROUP = 'usergroup:%s'
312 REPO_USERGROUP = 'usergroup:%s'
313 REPO_OWNER = 'repo.owner'
313 REPO_OWNER = 'repo.owner'
314 REPO_DEFAULT = 'repo.default'
314 REPO_DEFAULT = 'repo.default'
315 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
315 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
316 REPO_PRIVATE = 'repo.private'
316 REPO_PRIVATE = 'repo.private'
317
317
318 REPOGROUP_USER = 'user:%s'
318 REPOGROUP_USER = 'user:%s'
319 REPOGROUP_USERGROUP = 'usergroup:%s'
319 REPOGROUP_USERGROUP = 'usergroup:%s'
320 REPOGROUP_OWNER = 'group.owner'
320 REPOGROUP_OWNER = 'group.owner'
321 REPOGROUP_DEFAULT = 'group.default'
321 REPOGROUP_DEFAULT = 'group.default'
322 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
322 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
323
323
324 USERGROUP_USER = 'user:%s'
324 USERGROUP_USER = 'user:%s'
325 USERGROUP_USERGROUP = 'usergroup:%s'
325 USERGROUP_USERGROUP = 'usergroup:%s'
326 USERGROUP_OWNER = 'usergroup.owner'
326 USERGROUP_OWNER = 'usergroup.owner'
327 USERGROUP_DEFAULT = 'usergroup.default'
327 USERGROUP_DEFAULT = 'usergroup.default'
328 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
328 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
329
329
330
330
331 class PermOriginDict(dict):
331 class PermOriginDict(dict):
332 """
332 """
333 A special dict used for tracking permissions along with their origins.
333 A special dict used for tracking permissions along with their origins.
334
334
335 `__setitem__` has been overridden to expect a tuple(perm, origin)
335 `__setitem__` has been overridden to expect a tuple(perm, origin)
336 `__getitem__` will return only the perm
336 `__getitem__` will return only the perm
337 `.perm_origin_stack` will return the stack of (perm, origin) set per key
337 `.perm_origin_stack` will return the stack of (perm, origin) set per key
338
338
339 >>> perms = PermOriginDict()
339 >>> perms = PermOriginDict()
340 >>> perms['resource'] = 'read', 'default'
340 >>> perms['resource'] = 'read', 'default'
341 >>> perms['resource']
341 >>> perms['resource']
342 'read'
342 'read'
343 >>> perms['resource'] = 'write', 'admin'
343 >>> perms['resource'] = 'write', 'admin'
344 >>> perms['resource']
344 >>> perms['resource']
345 'write'
345 'write'
346 >>> perms.perm_origin_stack
346 >>> perms.perm_origin_stack
347 {'resource': [('read', 'default'), ('write', 'admin')]}
347 {'resource': [('read', 'default'), ('write', 'admin')]}
348 """
348 """
349
349
350 def __init__(self, *args, **kw):
350 def __init__(self, *args, **kw):
351 dict.__init__(self, *args, **kw)
351 dict.__init__(self, *args, **kw)
352 self.perm_origin_stack = collections.OrderedDict()
352 self.perm_origin_stack = collections.OrderedDict()
353
353
354 def __setitem__(self, key, (perm, origin)):
354 def __setitem__(self, key, (perm, origin)):
355 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
355 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
356 dict.__setitem__(self, key, perm)
356 dict.__setitem__(self, key, perm)
357
357
358
358
359 class PermissionCalculator(object):
359 class PermissionCalculator(object):
360
360
361 def __init__(
361 def __init__(
362 self, user_id, scope, user_is_admin,
362 self, user_id, scope, user_is_admin,
363 user_inherit_default_permissions, explicit, algo,
363 user_inherit_default_permissions, explicit, algo,
364 calculate_super_admin=False):
364 calculate_super_admin=False):
365
365
366 self.user_id = user_id
366 self.user_id = user_id
367 self.user_is_admin = user_is_admin
367 self.user_is_admin = user_is_admin
368 self.inherit_default_permissions = user_inherit_default_permissions
368 self.inherit_default_permissions = user_inherit_default_permissions
369 self.explicit = explicit
369 self.explicit = explicit
370 self.algo = algo
370 self.algo = algo
371 self.calculate_super_admin = calculate_super_admin
371 self.calculate_super_admin = calculate_super_admin
372
372
373 scope = scope or {}
373 scope = scope or {}
374 self.scope_repo_id = scope.get('repo_id')
374 self.scope_repo_id = scope.get('repo_id')
375 self.scope_repo_group_id = scope.get('repo_group_id')
375 self.scope_repo_group_id = scope.get('repo_group_id')
376 self.scope_user_group_id = scope.get('user_group_id')
376 self.scope_user_group_id = scope.get('user_group_id')
377
377
378 self.default_user_id = User.get_default_user(cache=True).user_id
378 self.default_user_id = User.get_default_user(cache=True).user_id
379
379
380 self.permissions_repositories = PermOriginDict()
380 self.permissions_repositories = PermOriginDict()
381 self.permissions_repository_groups = PermOriginDict()
381 self.permissions_repository_groups = PermOriginDict()
382 self.permissions_user_groups = PermOriginDict()
382 self.permissions_user_groups = PermOriginDict()
383 self.permissions_global = set()
383 self.permissions_global = set()
384
384
385 self.default_repo_perms = Permission.get_default_repo_perms(
385 self.default_repo_perms = Permission.get_default_repo_perms(
386 self.default_user_id, self.scope_repo_id)
386 self.default_user_id, self.scope_repo_id)
387 self.default_repo_groups_perms = Permission.get_default_group_perms(
387 self.default_repo_groups_perms = Permission.get_default_group_perms(
388 self.default_user_id, self.scope_repo_group_id)
388 self.default_user_id, self.scope_repo_group_id)
389 self.default_user_group_perms = \
389 self.default_user_group_perms = \
390 Permission.get_default_user_group_perms(
390 Permission.get_default_user_group_perms(
391 self.default_user_id, self.scope_user_group_id)
391 self.default_user_id, self.scope_user_group_id)
392
392
393 def calculate(self):
393 def calculate(self):
394 if self.user_is_admin and not self.calculate_super_admin:
394 if self.user_is_admin and not self.calculate_super_admin:
395 return self._admin_permissions()
395 return self._admin_permissions()
396
396
397 self._calculate_global_default_permissions()
397 self._calculate_global_default_permissions()
398 self._calculate_global_permissions()
398 self._calculate_global_permissions()
399 self._calculate_default_permissions()
399 self._calculate_default_permissions()
400 self._calculate_repository_permissions()
400 self._calculate_repository_permissions()
401 self._calculate_repository_group_permissions()
401 self._calculate_repository_group_permissions()
402 self._calculate_user_group_permissions()
402 self._calculate_user_group_permissions()
403 return self._permission_structure()
403 return self._permission_structure()
404
404
405 def _admin_permissions(self):
405 def _admin_permissions(self):
406 """
406 """
407 admin user have all default rights for repositories
407 admin user have all default rights for repositories
408 and groups set to admin
408 and groups set to admin
409 """
409 """
410 self.permissions_global.add('hg.admin')
410 self.permissions_global.add('hg.admin')
411 self.permissions_global.add('hg.create.write_on_repogroup.true')
411 self.permissions_global.add('hg.create.write_on_repogroup.true')
412
412
413 # repositories
413 # repositories
414 for perm in self.default_repo_perms:
414 for perm in self.default_repo_perms:
415 r_k = perm.UserRepoToPerm.repository.repo_name
415 r_k = perm.UserRepoToPerm.repository.repo_name
416 p = 'repository.admin'
416 p = 'repository.admin'
417 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
417 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
418
418
419 # repository groups
419 # repository groups
420 for perm in self.default_repo_groups_perms:
420 for perm in self.default_repo_groups_perms:
421 rg_k = perm.UserRepoGroupToPerm.group.group_name
421 rg_k = perm.UserRepoGroupToPerm.group.group_name
422 p = 'group.admin'
422 p = 'group.admin'
423 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
423 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
424
424
425 # user groups
425 # user groups
426 for perm in self.default_user_group_perms:
426 for perm in self.default_user_group_perms:
427 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
427 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
428 p = 'usergroup.admin'
428 p = 'usergroup.admin'
429 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
429 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
430
430
431 return self._permission_structure()
431 return self._permission_structure()
432
432
433 def _calculate_global_default_permissions(self):
433 def _calculate_global_default_permissions(self):
434 """
434 """
435 global permissions taken from the default user
435 global permissions taken from the default user
436 """
436 """
437 default_global_perms = UserToPerm.query()\
437 default_global_perms = UserToPerm.query()\
438 .filter(UserToPerm.user_id == self.default_user_id)\
438 .filter(UserToPerm.user_id == self.default_user_id)\
439 .options(joinedload(UserToPerm.permission))
439 .options(joinedload(UserToPerm.permission))
440
440
441 for perm in default_global_perms:
441 for perm in default_global_perms:
442 self.permissions_global.add(perm.permission.permission_name)
442 self.permissions_global.add(perm.permission.permission_name)
443
443
444 if self.user_is_admin:
444 if self.user_is_admin:
445 self.permissions_global.add('hg.admin')
445 self.permissions_global.add('hg.admin')
446 self.permissions_global.add('hg.create.write_on_repogroup.true')
446 self.permissions_global.add('hg.create.write_on_repogroup.true')
447
447
448 def _calculate_global_permissions(self):
448 def _calculate_global_permissions(self):
449 """
449 """
450 Set global system permissions with user permissions or permissions
450 Set global system permissions with user permissions or permissions
451 taken from the user groups of the current user.
451 taken from the user groups of the current user.
452
452
453 The permissions include repo creating, repo group creating, forking
453 The permissions include repo creating, repo group creating, forking
454 etc.
454 etc.
455 """
455 """
456
456
457 # now we read the defined permissions and overwrite what we have set
457 # now we read the defined permissions and overwrite what we have set
458 # before those can be configured from groups or users explicitly.
458 # before those can be configured from groups or users explicitly.
459
459
460 # TODO: johbo: This seems to be out of sync, find out the reason
460 # TODO: johbo: This seems to be out of sync, find out the reason
461 # for the comment below and update it.
461 # for the comment below and update it.
462
462
463 # In case we want to extend this list we should be always in sync with
463 # In case we want to extend this list we should be always in sync with
464 # User.DEFAULT_USER_PERMISSIONS definitions
464 # User.DEFAULT_USER_PERMISSIONS definitions
465 _configurable = frozenset([
465 _configurable = frozenset([
466 'hg.fork.none', 'hg.fork.repository',
466 'hg.fork.none', 'hg.fork.repository',
467 'hg.create.none', 'hg.create.repository',
467 'hg.create.none', 'hg.create.repository',
468 'hg.usergroup.create.false', 'hg.usergroup.create.true',
468 'hg.usergroup.create.false', 'hg.usergroup.create.true',
469 'hg.repogroup.create.false', 'hg.repogroup.create.true',
469 'hg.repogroup.create.false', 'hg.repogroup.create.true',
470 'hg.create.write_on_repogroup.false',
470 'hg.create.write_on_repogroup.false',
471 'hg.create.write_on_repogroup.true',
471 'hg.create.write_on_repogroup.true',
472 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
472 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
473 ])
473 ])
474
474
475 # USER GROUPS comes first user group global permissions
475 # USER GROUPS comes first user group global permissions
476 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
476 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
477 .options(joinedload(UserGroupToPerm.permission))\
477 .options(joinedload(UserGroupToPerm.permission))\
478 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
478 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
479 UserGroupMember.users_group_id))\
479 UserGroupMember.users_group_id))\
480 .filter(UserGroupMember.user_id == self.user_id)\
480 .filter(UserGroupMember.user_id == self.user_id)\
481 .order_by(UserGroupToPerm.users_group_id)\
481 .order_by(UserGroupToPerm.users_group_id)\
482 .all()
482 .all()
483
483
484 # need to group here by groups since user can be in more than
484 # need to group here by groups since user can be in more than
485 # one group, so we get all groups
485 # one group, so we get all groups
486 _explicit_grouped_perms = [
486 _explicit_grouped_perms = [
487 [x, list(y)] for x, y in
487 [x, list(y)] for x, y in
488 itertools.groupby(user_perms_from_users_groups,
488 itertools.groupby(user_perms_from_users_groups,
489 lambda _x: _x.users_group)]
489 lambda _x: _x.users_group)]
490
490
491 for gr, perms in _explicit_grouped_perms:
491 for gr, perms in _explicit_grouped_perms:
492 # since user can be in multiple groups iterate over them and
492 # since user can be in multiple groups iterate over them and
493 # select the lowest permissions first (more explicit)
493 # select the lowest permissions first (more explicit)
494 # TODO: marcink: do this^^
494 # TODO: marcink: do this^^
495
495
496 # group doesn't inherit default permissions so we actually set them
496 # group doesn't inherit default permissions so we actually set them
497 if not gr.inherit_default_permissions:
497 if not gr.inherit_default_permissions:
498 # NEED TO IGNORE all previously set configurable permissions
498 # NEED TO IGNORE all previously set configurable permissions
499 # and replace them with explicitly set from this user
499 # and replace them with explicitly set from this user
500 # group permissions
500 # group permissions
501 self.permissions_global = self.permissions_global.difference(
501 self.permissions_global = self.permissions_global.difference(
502 _configurable)
502 _configurable)
503 for perm in perms:
503 for perm in perms:
504 self.permissions_global.add(perm.permission.permission_name)
504 self.permissions_global.add(perm.permission.permission_name)
505
505
506 # user explicit global permissions
506 # user explicit global permissions
507 user_perms = Session().query(UserToPerm)\
507 user_perms = Session().query(UserToPerm)\
508 .options(joinedload(UserToPerm.permission))\
508 .options(joinedload(UserToPerm.permission))\
509 .filter(UserToPerm.user_id == self.user_id).all()
509 .filter(UserToPerm.user_id == self.user_id).all()
510
510
511 if not self.inherit_default_permissions:
511 if not self.inherit_default_permissions:
512 # NEED TO IGNORE all configurable permissions and
512 # NEED TO IGNORE all configurable permissions and
513 # replace them with explicitly set from this user permissions
513 # replace them with explicitly set from this user permissions
514 self.permissions_global = self.permissions_global.difference(
514 self.permissions_global = self.permissions_global.difference(
515 _configurable)
515 _configurable)
516 for perm in user_perms:
516 for perm in user_perms:
517 self.permissions_global.add(perm.permission.permission_name)
517 self.permissions_global.add(perm.permission.permission_name)
518
518
519 def _calculate_default_permissions(self):
519 def _calculate_default_permissions(self):
520 """
520 """
521 Set default user permissions for repositories, repository groups
521 Set default user permissions for repositories, repository groups
522 taken from the default user.
522 taken from the default user.
523
523
524 Calculate inheritance of object permissions based on what we have now
524 Calculate inheritance of object permissions based on what we have now
525 in GLOBAL permissions. We check if .false is in GLOBAL since this is
525 in GLOBAL permissions. We check if .false is in GLOBAL since this is
526 explicitly set. Inherit is the opposite of .false being there.
526 explicitly set. Inherit is the opposite of .false being there.
527
527
528 .. note::
528 .. note::
529
529
530 the syntax is little bit odd but what we need to check here is
530 the syntax is little bit odd but what we need to check here is
531 the opposite of .false permission being in the list so even for
531 the opposite of .false permission being in the list so even for
532 inconsistent state when both .true/.false is there
532 inconsistent state when both .true/.false is there
533 .false is more important
533 .false is more important
534
534
535 """
535 """
536 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
536 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
537 in self.permissions_global)
537 in self.permissions_global)
538
538
539 # defaults for repositories, taken from `default` user permissions
539 # defaults for repositories, taken from `default` user permissions
540 # on given repo
540 # on given repo
541 for perm in self.default_repo_perms:
541 for perm in self.default_repo_perms:
542 r_k = perm.UserRepoToPerm.repository.repo_name
542 r_k = perm.UserRepoToPerm.repository.repo_name
543 p = perm.Permission.permission_name
543 p = perm.Permission.permission_name
544 o = PermOrigin.REPO_DEFAULT
544 o = PermOrigin.REPO_DEFAULT
545 self.permissions_repositories[r_k] = p, o
545 self.permissions_repositories[r_k] = p, o
546
546
547 # if we decide this user isn't inheriting permissions from
547 # if we decide this user isn't inheriting permissions from
548 # default user we set him to .none so only explicit
548 # default user we set him to .none so only explicit
549 # permissions work
549 # permissions work
550 if not user_inherit_object_permissions:
550 if not user_inherit_object_permissions:
551 p = 'repository.none'
551 p = 'repository.none'
552 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
552 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
553 self.permissions_repositories[r_k] = p, o
553 self.permissions_repositories[r_k] = p, o
554
554
555 if perm.Repository.private and not (
555 if perm.Repository.private and not (
556 perm.Repository.user_id == self.user_id):
556 perm.Repository.user_id == self.user_id):
557 # disable defaults for private repos,
557 # disable defaults for private repos,
558 p = 'repository.none'
558 p = 'repository.none'
559 o = PermOrigin.REPO_PRIVATE
559 o = PermOrigin.REPO_PRIVATE
560 self.permissions_repositories[r_k] = p, o
560 self.permissions_repositories[r_k] = p, o
561
561
562 elif perm.Repository.user_id == self.user_id:
562 elif perm.Repository.user_id == self.user_id:
563 # set admin if owner
563 # set admin if owner
564 p = 'repository.admin'
564 p = 'repository.admin'
565 o = PermOrigin.REPO_OWNER
565 o = PermOrigin.REPO_OWNER
566 self.permissions_repositories[r_k] = p, o
566 self.permissions_repositories[r_k] = p, o
567
567
568 if self.user_is_admin:
568 if self.user_is_admin:
569 p = 'repository.admin'
569 p = 'repository.admin'
570 o = PermOrigin.SUPER_ADMIN
570 o = PermOrigin.SUPER_ADMIN
571 self.permissions_repositories[r_k] = p, o
571 self.permissions_repositories[r_k] = p, o
572
572
573 # defaults for repository groups taken from `default` user permission
573 # defaults for repository groups taken from `default` user permission
574 # on given group
574 # on given group
575 for perm in self.default_repo_groups_perms:
575 for perm in self.default_repo_groups_perms:
576 rg_k = perm.UserRepoGroupToPerm.group.group_name
576 rg_k = perm.UserRepoGroupToPerm.group.group_name
577 p = perm.Permission.permission_name
577 p = perm.Permission.permission_name
578 o = PermOrigin.REPOGROUP_DEFAULT
578 o = PermOrigin.REPOGROUP_DEFAULT
579 self.permissions_repository_groups[rg_k] = p, o
579 self.permissions_repository_groups[rg_k] = p, o
580
580
581 # if we decide this user isn't inheriting permissions from default
581 # if we decide this user isn't inheriting permissions from default
582 # user we set him to .none so only explicit permissions work
582 # user we set him to .none so only explicit permissions work
583 if not user_inherit_object_permissions:
583 if not user_inherit_object_permissions:
584 p = 'group.none'
584 p = 'group.none'
585 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
585 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
586 self.permissions_repository_groups[rg_k] = p, o
586 self.permissions_repository_groups[rg_k] = p, o
587
587
588 if perm.RepoGroup.user_id == self.user_id:
588 if perm.RepoGroup.user_id == self.user_id:
589 # set admin if owner
589 # set admin if owner
590 p = 'group.admin'
590 p = 'group.admin'
591 o = PermOrigin.REPOGROUP_OWNER
591 o = PermOrigin.REPOGROUP_OWNER
592 self.permissions_repository_groups[rg_k] = p, o
592 self.permissions_repository_groups[rg_k] = p, o
593
593
594 if self.user_is_admin:
594 if self.user_is_admin:
595 p = 'group.admin'
595 p = 'group.admin'
596 o = PermOrigin.SUPER_ADMIN
596 o = PermOrigin.SUPER_ADMIN
597 self.permissions_repository_groups[rg_k] = p, o
597 self.permissions_repository_groups[rg_k] = p, o
598
598
599 # defaults for user groups taken from `default` user permission
599 # defaults for user groups taken from `default` user permission
600 # on given user group
600 # on given user group
601 for perm in self.default_user_group_perms:
601 for perm in self.default_user_group_perms:
602 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
602 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
603 p = perm.Permission.permission_name
603 p = perm.Permission.permission_name
604 o = PermOrigin.USERGROUP_DEFAULT
604 o = PermOrigin.USERGROUP_DEFAULT
605 self.permissions_user_groups[u_k] = p, o
605 self.permissions_user_groups[u_k] = p, o
606
606
607 # if we decide this user isn't inheriting permissions from default
607 # if we decide this user isn't inheriting permissions from default
608 # user we set him to .none so only explicit permissions work
608 # user we set him to .none so only explicit permissions work
609 if not user_inherit_object_permissions:
609 if not user_inherit_object_permissions:
610 p = 'usergroup.none'
610 p = 'usergroup.none'
611 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
611 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
612 self.permissions_user_groups[u_k] = p, o
612 self.permissions_user_groups[u_k] = p, o
613
613
614 if perm.UserGroup.user_id == self.user_id:
614 if perm.UserGroup.user_id == self.user_id:
615 # set admin if owner
615 # set admin if owner
616 p = 'usergroup.admin'
616 p = 'usergroup.admin'
617 o = PermOrigin.USERGROUP_OWNER
617 o = PermOrigin.USERGROUP_OWNER
618 self.permissions_user_groups[u_k] = p, o
618 self.permissions_user_groups[u_k] = p, o
619
619
620 if self.user_is_admin:
620 if self.user_is_admin:
621 p = 'usergroup.admin'
621 p = 'usergroup.admin'
622 o = PermOrigin.SUPER_ADMIN
622 o = PermOrigin.SUPER_ADMIN
623 self.permissions_user_groups[u_k] = p, o
623 self.permissions_user_groups[u_k] = p, o
624
624
625 def _calculate_repository_permissions(self):
625 def _calculate_repository_permissions(self):
626 """
626 """
627 Repository permissions for the current user.
627 Repository permissions for the current user.
628
628
629 Check if the user is part of user groups for this repository and
629 Check if the user is part of user groups for this repository and
630 fill in the permission from it. `_choose_permission` decides of which
630 fill in the permission from it. `_choose_permission` decides of which
631 permission should be selected based on selected method.
631 permission should be selected based on selected method.
632 """
632 """
633
633
634 # user group for repositories permissions
634 # user group for repositories permissions
635 user_repo_perms_from_user_group = Permission\
635 user_repo_perms_from_user_group = Permission\
636 .get_default_repo_perms_from_user_group(
636 .get_default_repo_perms_from_user_group(
637 self.user_id, self.scope_repo_id)
637 self.user_id, self.scope_repo_id)
638
638
639 multiple_counter = collections.defaultdict(int)
639 multiple_counter = collections.defaultdict(int)
640 for perm in user_repo_perms_from_user_group:
640 for perm in user_repo_perms_from_user_group:
641 r_k = perm.UserGroupRepoToPerm.repository.repo_name
641 r_k = perm.UserGroupRepoToPerm.repository.repo_name
642 multiple_counter[r_k] += 1
642 multiple_counter[r_k] += 1
643 p = perm.Permission.permission_name
643 p = perm.Permission.permission_name
644 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
644 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
645 .users_group.users_group_name
645 .users_group.users_group_name
646
646
647 if multiple_counter[r_k] > 1:
647 if multiple_counter[r_k] > 1:
648 cur_perm = self.permissions_repositories[r_k]
648 cur_perm = self.permissions_repositories[r_k]
649 p = self._choose_permission(p, cur_perm)
649 p = self._choose_permission(p, cur_perm)
650
650
651 self.permissions_repositories[r_k] = p, o
651 self.permissions_repositories[r_k] = p, o
652
652
653 if perm.Repository.user_id == self.user_id:
653 if perm.Repository.user_id == self.user_id:
654 # set admin if owner
654 # set admin if owner
655 p = 'repository.admin'
655 p = 'repository.admin'
656 o = PermOrigin.REPO_OWNER
656 o = PermOrigin.REPO_OWNER
657 self.permissions_repositories[r_k] = p, o
657 self.permissions_repositories[r_k] = p, o
658
658
659 if self.user_is_admin:
659 if self.user_is_admin:
660 p = 'repository.admin'
660 p = 'repository.admin'
661 o = PermOrigin.SUPER_ADMIN
661 o = PermOrigin.SUPER_ADMIN
662 self.permissions_repositories[r_k] = p, o
662 self.permissions_repositories[r_k] = p, o
663
663
664 # user explicit permissions for repositories, overrides any specified
664 # user explicit permissions for repositories, overrides any specified
665 # by the group permission
665 # by the group permission
666 user_repo_perms = Permission.get_default_repo_perms(
666 user_repo_perms = Permission.get_default_repo_perms(
667 self.user_id, self.scope_repo_id)
667 self.user_id, self.scope_repo_id)
668 for perm in user_repo_perms:
668 for perm in user_repo_perms:
669 r_k = perm.UserRepoToPerm.repository.repo_name
669 r_k = perm.UserRepoToPerm.repository.repo_name
670 p = perm.Permission.permission_name
670 p = perm.Permission.permission_name
671 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
671 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
672
672
673 if not self.explicit:
673 if not self.explicit:
674 cur_perm = self.permissions_repositories.get(
674 cur_perm = self.permissions_repositories.get(
675 r_k, 'repository.none')
675 r_k, 'repository.none')
676 p = self._choose_permission(p, cur_perm)
676 p = self._choose_permission(p, cur_perm)
677
677
678 self.permissions_repositories[r_k] = p, o
678 self.permissions_repositories[r_k] = p, o
679
679
680 if perm.Repository.user_id == self.user_id:
680 if perm.Repository.user_id == self.user_id:
681 # set admin if owner
681 # set admin if owner
682 p = 'repository.admin'
682 p = 'repository.admin'
683 o = PermOrigin.REPO_OWNER
683 o = PermOrigin.REPO_OWNER
684 self.permissions_repositories[r_k] = p, o
684 self.permissions_repositories[r_k] = p, o
685
685
686 if self.user_is_admin:
686 if self.user_is_admin:
687 p = 'repository.admin'
687 p = 'repository.admin'
688 o = PermOrigin.SUPER_ADMIN
688 o = PermOrigin.SUPER_ADMIN
689 self.permissions_repositories[r_k] = p, o
689 self.permissions_repositories[r_k] = p, o
690
690
691 def _calculate_repository_group_permissions(self):
691 def _calculate_repository_group_permissions(self):
692 """
692 """
693 Repository group permissions for the current user.
693 Repository group permissions for the current user.
694
694
695 Check if the user is part of user groups for repository groups and
695 Check if the user is part of user groups for repository groups and
696 fill in the permissions from it. `_choose_permission` decides of which
696 fill in the permissions from it. `_choose_permission` decides of which
697 permission should be selected based on selected method.
697 permission should be selected based on selected method.
698 """
698 """
699 # user group for repo groups permissions
699 # user group for repo groups permissions
700 user_repo_group_perms_from_user_group = Permission\
700 user_repo_group_perms_from_user_group = Permission\
701 .get_default_group_perms_from_user_group(
701 .get_default_group_perms_from_user_group(
702 self.user_id, self.scope_repo_group_id)
702 self.user_id, self.scope_repo_group_id)
703
703
704 multiple_counter = collections.defaultdict(int)
704 multiple_counter = collections.defaultdict(int)
705 for perm in user_repo_group_perms_from_user_group:
705 for perm in user_repo_group_perms_from_user_group:
706 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
706 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
707 multiple_counter[rg_k] += 1
707 multiple_counter[rg_k] += 1
708 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
708 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
709 .users_group.users_group_name
709 .users_group.users_group_name
710 p = perm.Permission.permission_name
710 p = perm.Permission.permission_name
711
711
712 if multiple_counter[rg_k] > 1:
712 if multiple_counter[rg_k] > 1:
713 cur_perm = self.permissions_repository_groups[rg_k]
713 cur_perm = self.permissions_repository_groups[rg_k]
714 p = self._choose_permission(p, cur_perm)
714 p = self._choose_permission(p, cur_perm)
715 self.permissions_repository_groups[rg_k] = p, o
715 self.permissions_repository_groups[rg_k] = p, o
716
716
717 if perm.RepoGroup.user_id == self.user_id:
717 if perm.RepoGroup.user_id == self.user_id:
718 # set admin if owner, even for member of other user group
718 # set admin if owner, even for member of other user group
719 p = 'group.admin'
719 p = 'group.admin'
720 o = PermOrigin.REPOGROUP_OWNER
720 o = PermOrigin.REPOGROUP_OWNER
721 self.permissions_repository_groups[rg_k] = p, o
721 self.permissions_repository_groups[rg_k] = p, o
722
722
723 if self.user_is_admin:
723 if self.user_is_admin:
724 p = 'group.admin'
724 p = 'group.admin'
725 o = PermOrigin.SUPER_ADMIN
725 o = PermOrigin.SUPER_ADMIN
726 self.permissions_repository_groups[rg_k] = p, o
726 self.permissions_repository_groups[rg_k] = p, o
727
727
728 # user explicit permissions for repository groups
728 # user explicit permissions for repository groups
729 user_repo_groups_perms = Permission.get_default_group_perms(
729 user_repo_groups_perms = Permission.get_default_group_perms(
730 self.user_id, self.scope_repo_group_id)
730 self.user_id, self.scope_repo_group_id)
731 for perm in user_repo_groups_perms:
731 for perm in user_repo_groups_perms:
732 rg_k = perm.UserRepoGroupToPerm.group.group_name
732 rg_k = perm.UserRepoGroupToPerm.group.group_name
733 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
733 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
734 .user.username
734 .user.username
735 p = perm.Permission.permission_name
735 p = perm.Permission.permission_name
736
736
737 if not self.explicit:
737 if not self.explicit:
738 cur_perm = self.permissions_repository_groups.get(
738 cur_perm = self.permissions_repository_groups.get(
739 rg_k, 'group.none')
739 rg_k, 'group.none')
740 p = self._choose_permission(p, cur_perm)
740 p = self._choose_permission(p, cur_perm)
741
741
742 self.permissions_repository_groups[rg_k] = p, o
742 self.permissions_repository_groups[rg_k] = p, o
743
743
744 if perm.RepoGroup.user_id == self.user_id:
744 if perm.RepoGroup.user_id == self.user_id:
745 # set admin if owner
745 # set admin if owner
746 p = 'group.admin'
746 p = 'group.admin'
747 o = PermOrigin.REPOGROUP_OWNER
747 o = PermOrigin.REPOGROUP_OWNER
748 self.permissions_repository_groups[rg_k] = p, o
748 self.permissions_repository_groups[rg_k] = p, o
749
749
750 if self.user_is_admin:
750 if self.user_is_admin:
751 p = 'group.admin'
751 p = 'group.admin'
752 o = PermOrigin.SUPER_ADMIN
752 o = PermOrigin.SUPER_ADMIN
753 self.permissions_repository_groups[rg_k] = p, o
753 self.permissions_repository_groups[rg_k] = p, o
754
754
755 def _calculate_user_group_permissions(self):
755 def _calculate_user_group_permissions(self):
756 """
756 """
757 User group permissions for the current user.
757 User group permissions for the current user.
758 """
758 """
759 # user group for user group permissions
759 # user group for user group permissions
760 user_group_from_user_group = Permission\
760 user_group_from_user_group = Permission\
761 .get_default_user_group_perms_from_user_group(
761 .get_default_user_group_perms_from_user_group(
762 self.user_id, self.scope_user_group_id)
762 self.user_id, self.scope_user_group_id)
763
763
764 multiple_counter = collections.defaultdict(int)
764 multiple_counter = collections.defaultdict(int)
765 for perm in user_group_from_user_group:
765 for perm in user_group_from_user_group:
766 ug_k = perm.UserGroupUserGroupToPerm\
766 ug_k = perm.UserGroupUserGroupToPerm\
767 .target_user_group.users_group_name
767 .target_user_group.users_group_name
768 multiple_counter[ug_k] += 1
768 multiple_counter[ug_k] += 1
769 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
769 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
770 .user_group.users_group_name
770 .user_group.users_group_name
771 p = perm.Permission.permission_name
771 p = perm.Permission.permission_name
772
772
773 if multiple_counter[ug_k] > 1:
773 if multiple_counter[ug_k] > 1:
774 cur_perm = self.permissions_user_groups[ug_k]
774 cur_perm = self.permissions_user_groups[ug_k]
775 p = self._choose_permission(p, cur_perm)
775 p = self._choose_permission(p, cur_perm)
776
776
777 self.permissions_user_groups[ug_k] = p, o
777 self.permissions_user_groups[ug_k] = p, o
778
778
779 if perm.UserGroup.user_id == self.user_id:
779 if perm.UserGroup.user_id == self.user_id:
780 # set admin if owner, even for member of other user group
780 # set admin if owner, even for member of other user group
781 p = 'usergroup.admin'
781 p = 'usergroup.admin'
782 o = PermOrigin.USERGROUP_OWNER
782 o = PermOrigin.USERGROUP_OWNER
783 self.permissions_user_groups[ug_k] = p, o
783 self.permissions_user_groups[ug_k] = p, o
784
784
785 if self.user_is_admin:
785 if self.user_is_admin:
786 p = 'usergroup.admin'
786 p = 'usergroup.admin'
787 o = PermOrigin.SUPER_ADMIN
787 o = PermOrigin.SUPER_ADMIN
788 self.permissions_user_groups[ug_k] = p, o
788 self.permissions_user_groups[ug_k] = p, o
789
789
790 # user explicit permission for user groups
790 # user explicit permission for user groups
791 user_user_groups_perms = Permission.get_default_user_group_perms(
791 user_user_groups_perms = Permission.get_default_user_group_perms(
792 self.user_id, self.scope_user_group_id)
792 self.user_id, self.scope_user_group_id)
793 for perm in user_user_groups_perms:
793 for perm in user_user_groups_perms:
794 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
794 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
795 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
795 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
796 .user.username
796 .user.username
797 p = perm.Permission.permission_name
797 p = perm.Permission.permission_name
798
798
799 if not self.explicit:
799 if not self.explicit:
800 cur_perm = self.permissions_user_groups.get(
800 cur_perm = self.permissions_user_groups.get(
801 ug_k, 'usergroup.none')
801 ug_k, 'usergroup.none')
802 p = self._choose_permission(p, cur_perm)
802 p = self._choose_permission(p, cur_perm)
803
803
804 self.permissions_user_groups[ug_k] = p, o
804 self.permissions_user_groups[ug_k] = p, o
805
805
806 if perm.UserGroup.user_id == self.user_id:
806 if perm.UserGroup.user_id == self.user_id:
807 # set admin if owner
807 # set admin if owner
808 p = 'usergroup.admin'
808 p = 'usergroup.admin'
809 o = PermOrigin.USERGROUP_OWNER
809 o = PermOrigin.USERGROUP_OWNER
810 self.permissions_user_groups[ug_k] = p, o
810 self.permissions_user_groups[ug_k] = p, o
811
811
812 if self.user_is_admin:
812 if self.user_is_admin:
813 p = 'usergroup.admin'
813 p = 'usergroup.admin'
814 o = PermOrigin.SUPER_ADMIN
814 o = PermOrigin.SUPER_ADMIN
815 self.permissions_user_groups[ug_k] = p, o
815 self.permissions_user_groups[ug_k] = p, o
816
816
817 def _choose_permission(self, new_perm, cur_perm):
817 def _choose_permission(self, new_perm, cur_perm):
818 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
818 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
819 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
819 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
820 if self.algo == 'higherwin':
820 if self.algo == 'higherwin':
821 if new_perm_val > cur_perm_val:
821 if new_perm_val > cur_perm_val:
822 return new_perm
822 return new_perm
823 return cur_perm
823 return cur_perm
824 elif self.algo == 'lowerwin':
824 elif self.algo == 'lowerwin':
825 if new_perm_val < cur_perm_val:
825 if new_perm_val < cur_perm_val:
826 return new_perm
826 return new_perm
827 return cur_perm
827 return cur_perm
828
828
829 def _permission_structure(self):
829 def _permission_structure(self):
830 return {
830 return {
831 'global': self.permissions_global,
831 'global': self.permissions_global,
832 'repositories': self.permissions_repositories,
832 'repositories': self.permissions_repositories,
833 'repositories_groups': self.permissions_repository_groups,
833 'repositories_groups': self.permissions_repository_groups,
834 'user_groups': self.permissions_user_groups,
834 'user_groups': self.permissions_user_groups,
835 }
835 }
836
836
837
837
838 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
838 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
839 """
839 """
840 Check if given controller_name is in whitelist of auth token access
840 Check if given controller_name is in whitelist of auth token access
841 """
841 """
842 if not whitelist:
842 if not whitelist:
843 from rhodecode import CONFIG
843 from rhodecode import CONFIG
844 whitelist = aslist(
844 whitelist = aslist(
845 CONFIG.get('api_access_controllers_whitelist'), sep=',')
845 CONFIG.get('api_access_controllers_whitelist'), sep=',')
846 # backward compat translation
846 # backward compat translation
847 compat = {
847 compat = {
848 # old controller, new VIEW
848 # old controller, new VIEW
849 'ChangesetController:*': 'RepoCommitsView:*',
849 'ChangesetController:*': 'RepoCommitsView:*',
850 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
850 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
851 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
851 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
852 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
852 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
853 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
853 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
854 'GistsController:*': 'GistView:*',
854 'GistsController:*': 'GistView:*',
855 }
855 }
856
856
857 log.debug(
857 log.debug(
858 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
858 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
859 auth_token_access_valid = False
859 auth_token_access_valid = False
860
860
861 for entry in whitelist:
861 for entry in whitelist:
862 token_match = True
862 token_match = True
863 if entry in compat:
863 if entry in compat:
864 # translate from old Controllers to Pyramid Views
864 # translate from old Controllers to Pyramid Views
865 entry = compat[entry]
865 entry = compat[entry]
866
866
867 if '@' in entry:
867 if '@' in entry:
868 # specific AuthToken
868 # specific AuthToken
869 entry, allowed_token = entry.split('@', 1)
869 entry, allowed_token = entry.split('@', 1)
870 token_match = auth_token == allowed_token
870 token_match = auth_token == allowed_token
871
871
872 if fnmatch.fnmatch(view_name, entry) and token_match:
872 if fnmatch.fnmatch(view_name, entry) and token_match:
873 auth_token_access_valid = True
873 auth_token_access_valid = True
874 break
874 break
875
875
876 if auth_token_access_valid:
876 if auth_token_access_valid:
877 log.debug('view: `%s` matches entry in whitelist: %s'
877 log.debug('view: `%s` matches entry in whitelist: %s'
878 % (view_name, whitelist))
878 % (view_name, whitelist))
879 else:
879 else:
880 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
880 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
881 % (view_name, whitelist))
881 % (view_name, whitelist))
882 if auth_token:
882 if auth_token:
883 # if we use auth token key and don't have access it's a warning
883 # if we use auth token key and don't have access it's a warning
884 log.warning(msg)
884 log.warning(msg)
885 else:
885 else:
886 log.debug(msg)
886 log.debug(msg)
887
887
888 return auth_token_access_valid
888 return auth_token_access_valid
889
889
890
890
891 class AuthUser(object):
891 class AuthUser(object):
892 """
892 """
893 A simple object that handles all attributes of user in RhodeCode
893 A simple object that handles all attributes of user in RhodeCode
894
894
895 It does lookup based on API key,given user, or user present in session
895 It does lookup based on API key,given user, or user present in session
896 Then it fills all required information for such user. It also checks if
896 Then it fills all required information for such user. It also checks if
897 anonymous access is enabled and if so, it returns default user as logged in
897 anonymous access is enabled and if so, it returns default user as logged in
898 """
898 """
899 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
899 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
900
900
901 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
901 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
902
902
903 self.user_id = user_id
903 self.user_id = user_id
904 self._api_key = api_key
904 self._api_key = api_key
905
905
906 self.api_key = None
906 self.api_key = None
907 self.feed_token = ''
907 self.feed_token = ''
908 self.username = username
908 self.username = username
909 self.ip_addr = ip_addr
909 self.ip_addr = ip_addr
910 self.name = ''
910 self.name = ''
911 self.lastname = ''
911 self.lastname = ''
912 self.first_name = ''
912 self.first_name = ''
913 self.last_name = ''
913 self.last_name = ''
914 self.email = ''
914 self.email = ''
915 self.is_authenticated = False
915 self.is_authenticated = False
916 self.admin = False
916 self.admin = False
917 self.inherit_default_permissions = False
917 self.inherit_default_permissions = False
918 self.password = ''
918 self.password = ''
919
919
920 self.anonymous_user = None # propagated on propagate_data
920 self.anonymous_user = None # propagated on propagate_data
921 self.propagate_data()
921 self.propagate_data()
922 self._instance = None
922 self._instance = None
923 self._permissions_scoped_cache = {} # used to bind scoped calculation
923 self._permissions_scoped_cache = {} # used to bind scoped calculation
924
924
925 @LazyProperty
925 @LazyProperty
926 def permissions(self):
926 def permissions(self):
927 return self.get_perms(user=self, cache=False)
927 return self.get_perms(user=self, cache=False)
928
928
929 @LazyProperty
929 @LazyProperty
930 def permissions_full_details(self):
930 def permissions_full_details(self):
931 return self.get_perms(
931 return self.get_perms(
932 user=self, cache=False, calculate_super_admin=True)
932 user=self, cache=False, calculate_super_admin=True)
933
933
934 def permissions_with_scope(self, scope):
934 def permissions_with_scope(self, scope):
935 """
935 """
936 Call the get_perms function with scoped data. The scope in that function
936 Call the get_perms function with scoped data. The scope in that function
937 narrows the SQL calls to the given ID of objects resulting in fetching
937 narrows the SQL calls to the given ID of objects resulting in fetching
938 Just particular permission we want to obtain. If scope is an empty dict
938 Just particular permission we want to obtain. If scope is an empty dict
939 then it basically narrows the scope to GLOBAL permissions only.
939 then it basically narrows the scope to GLOBAL permissions only.
940
940
941 :param scope: dict
941 :param scope: dict
942 """
942 """
943 if 'repo_name' in scope:
943 if 'repo_name' in scope:
944 obj = Repository.get_by_repo_name(scope['repo_name'])
944 obj = Repository.get_by_repo_name(scope['repo_name'])
945 if obj:
945 if obj:
946 scope['repo_id'] = obj.repo_id
946 scope['repo_id'] = obj.repo_id
947 _scope = {
947 _scope = {
948 'repo_id': -1,
948 'repo_id': -1,
949 'user_group_id': -1,
949 'user_group_id': -1,
950 'repo_group_id': -1,
950 'repo_group_id': -1,
951 }
951 }
952 _scope.update(scope)
952 _scope.update(scope)
953 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
953 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
954 _scope.items())))
954 _scope.items())))
955 if cache_key not in self._permissions_scoped_cache:
955 if cache_key not in self._permissions_scoped_cache:
956 # store in cache to mimic how the @LazyProperty works,
956 # store in cache to mimic how the @LazyProperty works,
957 # the difference here is that we use the unique key calculated
957 # the difference here is that we use the unique key calculated
958 # from params and values
958 # from params and values
959 res = self.get_perms(user=self, cache=False, scope=_scope)
959 res = self.get_perms(user=self, cache=False, scope=_scope)
960 self._permissions_scoped_cache[cache_key] = res
960 self._permissions_scoped_cache[cache_key] = res
961 return self._permissions_scoped_cache[cache_key]
961 return self._permissions_scoped_cache[cache_key]
962
962
963 def get_instance(self):
963 def get_instance(self):
964 return User.get(self.user_id)
964 return User.get(self.user_id)
965
965
966 def update_lastactivity(self):
966 def update_lastactivity(self):
967 if self.user_id:
967 if self.user_id:
968 User.get(self.user_id).update_lastactivity()
968 User.get(self.user_id).update_lastactivity()
969
969
970 def propagate_data(self):
970 def propagate_data(self):
971 """
971 """
972 Fills in user data and propagates values to this instance. Maps fetched
972 Fills in user data and propagates values to this instance. Maps fetched
973 user attributes to this class instance attributes
973 user attributes to this class instance attributes
974 """
974 """
975 log.debug('AuthUser: starting data propagation for new potential user')
975 log.debug('AuthUser: starting data propagation for new potential user')
976 user_model = UserModel()
976 user_model = UserModel()
977 anon_user = self.anonymous_user = User.get_default_user(cache=True)
977 anon_user = self.anonymous_user = User.get_default_user(cache=True)
978 is_user_loaded = False
978 is_user_loaded = False
979
979
980 # lookup by userid
980 # lookup by userid
981 if self.user_id is not None and self.user_id != anon_user.user_id:
981 if self.user_id is not None and self.user_id != anon_user.user_id:
982 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
982 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
983 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
983 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
984
984
985 # try go get user by api key
985 # try go get user by api key
986 elif self._api_key and self._api_key != anon_user.api_key:
986 elif self._api_key and self._api_key != anon_user.api_key:
987 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
987 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
988 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
988 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
989
989
990 # lookup by username
990 # lookup by username
991 elif self.username:
991 elif self.username:
992 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
992 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
993 is_user_loaded = user_model.fill_data(self, username=self.username)
993 is_user_loaded = user_model.fill_data(self, username=self.username)
994 else:
994 else:
995 log.debug('No data in %s that could been used to log in', self)
995 log.debug('No data in %s that could been used to log in', self)
996
996
997 if not is_user_loaded:
997 if not is_user_loaded:
998 log.debug('Failed to load user. Fallback to default user')
998 log.debug('Failed to load user. Fallback to default user')
999 # if we cannot authenticate user try anonymous
999 # if we cannot authenticate user try anonymous
1000 if anon_user.active:
1000 if anon_user.active:
1001 user_model.fill_data(self, user_id=anon_user.user_id)
1001 user_model.fill_data(self, user_id=anon_user.user_id)
1002 # then we set this user is logged in
1002 # then we set this user is logged in
1003 self.is_authenticated = True
1003 self.is_authenticated = True
1004 else:
1004 else:
1005 # in case of disabled anonymous user we reset some of the
1005 # in case of disabled anonymous user we reset some of the
1006 # parameters so such user is "corrupted", skipping the fill_data
1006 # parameters so such user is "corrupted", skipping the fill_data
1007 for attr in ['user_id', 'username', 'admin', 'active']:
1007 for attr in ['user_id', 'username', 'admin', 'active']:
1008 setattr(self, attr, None)
1008 setattr(self, attr, None)
1009 self.is_authenticated = False
1009 self.is_authenticated = False
1010
1010
1011 if not self.username:
1011 if not self.username:
1012 self.username = 'None'
1012 self.username = 'None'
1013
1013
1014 log.debug('AuthUser: propagated user is now %s', self)
1014 log.debug('AuthUser: propagated user is now %s', self)
1015
1015
1016 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1016 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1017 calculate_super_admin=False, cache=False):
1017 calculate_super_admin=False, cache=False):
1018 """
1018 """
1019 Fills user permission attribute with permissions taken from database
1019 Fills user permission attribute with permissions taken from database
1020 works for permissions given for repositories, and for permissions that
1020 works for permissions given for repositories, and for permissions that
1021 are granted to groups
1021 are granted to groups
1022
1022
1023 :param user: instance of User object from database
1023 :param user: instance of User object from database
1024 :param explicit: In case there are permissions both for user and a group
1024 :param explicit: In case there are permissions both for user and a group
1025 that user is part of, explicit flag will defiine if user will
1025 that user is part of, explicit flag will defiine if user will
1026 explicitly override permissions from group, if it's False it will
1026 explicitly override permissions from group, if it's False it will
1027 make decision based on the algo
1027 make decision based on the algo
1028 :param algo: algorithm to decide what permission should be choose if
1028 :param algo: algorithm to decide what permission should be choose if
1029 it's multiple defined, eg user in two different groups. It also
1029 it's multiple defined, eg user in two different groups. It also
1030 decides if explicit flag is turned off how to specify the permission
1030 decides if explicit flag is turned off how to specify the permission
1031 for case when user is in a group + have defined separate permission
1031 for case when user is in a group + have defined separate permission
1032 """
1032 """
1033 user_id = user.user_id
1033 user_id = user.user_id
1034 user_is_admin = user.is_admin
1034 user_is_admin = user.is_admin
1035
1035
1036 # inheritance of global permissions like create repo/fork repo etc
1036 # inheritance of global permissions like create repo/fork repo etc
1037 user_inherit_default_permissions = user.inherit_default_permissions
1037 user_inherit_default_permissions = user.inherit_default_permissions
1038
1038
1039 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
1039 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
1040 compute = caches.conditional_cache(
1040 compute = caches.conditional_cache(
1041 'short_term', 'cache_desc',
1041 'short_term', 'cache_desc',
1042 condition=cache, func=_cached_perms_data)
1042 condition=cache, func=_cached_perms_data)
1043 result = compute(user_id, scope, user_is_admin,
1043 result = compute(user_id, scope, user_is_admin,
1044 user_inherit_default_permissions, explicit, algo,
1044 user_inherit_default_permissions, explicit, algo,
1045 calculate_super_admin)
1045 calculate_super_admin)
1046
1046
1047 result_repr = []
1047 result_repr = []
1048 for k in result:
1048 for k in result:
1049 result_repr.append((k, len(result[k])))
1049 result_repr.append((k, len(result[k])))
1050
1050
1051 log.debug('PERMISSION tree computed %s' % (result_repr,))
1051 log.debug('PERMISSION tree computed %s' % (result_repr,))
1052 return result
1052 return result
1053
1053
1054 @property
1054 @property
1055 def is_default(self):
1055 def is_default(self):
1056 return self.username == User.DEFAULT_USER
1056 return self.username == User.DEFAULT_USER
1057
1057
1058 @property
1058 @property
1059 def is_admin(self):
1059 def is_admin(self):
1060 return self.admin
1060 return self.admin
1061
1061
1062 @property
1062 @property
1063 def is_user_object(self):
1063 def is_user_object(self):
1064 return self.user_id is not None
1064 return self.user_id is not None
1065
1065
1066 @property
1066 @property
1067 def repositories_admin(self):
1067 def repositories_admin(self):
1068 """
1068 """
1069 Returns list of repositories you're an admin of
1069 Returns list of repositories you're an admin of
1070 """
1070 """
1071 return [
1071 return [
1072 x[0] for x in self.permissions['repositories'].iteritems()
1072 x[0] for x in self.permissions['repositories'].iteritems()
1073 if x[1] == 'repository.admin']
1073 if x[1] == 'repository.admin']
1074
1074
1075 @property
1075 @property
1076 def repository_groups_admin(self):
1076 def repository_groups_admin(self):
1077 """
1077 """
1078 Returns list of repository groups you're an admin of
1078 Returns list of repository groups you're an admin of
1079 """
1079 """
1080 return [
1080 return [
1081 x[0] for x in self.permissions['repositories_groups'].iteritems()
1081 x[0] for x in self.permissions['repositories_groups'].iteritems()
1082 if x[1] == 'group.admin']
1082 if x[1] == 'group.admin']
1083
1083
1084 @property
1084 @property
1085 def user_groups_admin(self):
1085 def user_groups_admin(self):
1086 """
1086 """
1087 Returns list of user groups you're an admin of
1087 Returns list of user groups you're an admin of
1088 """
1088 """
1089 return [
1089 return [
1090 x[0] for x in self.permissions['user_groups'].iteritems()
1090 x[0] for x in self.permissions['user_groups'].iteritems()
1091 if x[1] == 'usergroup.admin']
1091 if x[1] == 'usergroup.admin']
1092
1092
1093 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1093 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1094 """
1094 """
1095 Returns list of repository ids that user have access to based on given
1095 Returns list of repository ids that user have access to based on given
1096 perms. The cache flag should be only used in cases that are used for
1096 perms. The cache flag should be only used in cases that are used for
1097 display purposes, NOT IN ANY CASE for permission checks.
1097 display purposes, NOT IN ANY CASE for permission checks.
1098 """
1098 """
1099 from rhodecode.model.scm import RepoList
1099 from rhodecode.model.scm import RepoList
1100 if not perms:
1100 if not perms:
1101 perms = [
1101 perms = [
1102 'repository.read', 'repository.write', 'repository.admin']
1102 'repository.read', 'repository.write', 'repository.admin']
1103
1103
1104 def _cached_repo_acl(user_id, perm_def, name_filter):
1104 def _cached_repo_acl(user_id, perm_def, name_filter):
1105 qry = Repository.query()
1105 qry = Repository.query()
1106 if name_filter:
1106 if name_filter:
1107 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1107 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1108 qry = qry.filter(
1108 qry = qry.filter(
1109 Repository.repo_name.ilike(ilike_expression))
1109 Repository.repo_name.ilike(ilike_expression))
1110
1110
1111 return [x.repo_id for x in
1111 return [x.repo_id for x in
1112 RepoList(qry, perm_set=perm_def)]
1112 RepoList(qry, perm_set=perm_def)]
1113
1113
1114 compute = caches.conditional_cache(
1114 compute = caches.conditional_cache(
1115 'long_term', 'repo_acl_ids',
1115 'long_term', 'repo_acl_ids',
1116 condition=cache, func=_cached_repo_acl)
1116 condition=cache, func=_cached_repo_acl)
1117 return compute(self.user_id, perms, name_filter)
1117 return compute(self.user_id, perms, name_filter)
1118
1118
1119 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1119 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1120 """
1120 """
1121 Returns list of repository group ids that user have access to based on given
1121 Returns list of repository group ids that user have access to based on given
1122 perms. The cache flag should be only used in cases that are used for
1122 perms. The cache flag should be only used in cases that are used for
1123 display purposes, NOT IN ANY CASE for permission checks.
1123 display purposes, NOT IN ANY CASE for permission checks.
1124 """
1124 """
1125 from rhodecode.model.scm import RepoGroupList
1125 from rhodecode.model.scm import RepoGroupList
1126 if not perms:
1126 if not perms:
1127 perms = [
1127 perms = [
1128 'group.read', 'group.write', 'group.admin']
1128 'group.read', 'group.write', 'group.admin']
1129
1129
1130 def _cached_repo_group_acl(user_id, perm_def, name_filter):
1130 def _cached_repo_group_acl(user_id, perm_def, name_filter):
1131 qry = RepoGroup.query()
1131 qry = RepoGroup.query()
1132 if name_filter:
1132 if name_filter:
1133 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1133 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1134 qry = qry.filter(
1134 qry = qry.filter(
1135 RepoGroup.group_name.ilike(ilike_expression))
1135 RepoGroup.group_name.ilike(ilike_expression))
1136
1136
1137 return [x.group_id for x in
1137 return [x.group_id for x in
1138 RepoGroupList(qry, perm_set=perm_def)]
1138 RepoGroupList(qry, perm_set=perm_def)]
1139
1139
1140 compute = caches.conditional_cache(
1140 compute = caches.conditional_cache(
1141 'long_term', 'repo_group_acl_ids',
1141 'long_term', 'repo_group_acl_ids',
1142 condition=cache, func=_cached_repo_group_acl)
1142 condition=cache, func=_cached_repo_group_acl)
1143 return compute(self.user_id, perms, name_filter)
1143 return compute(self.user_id, perms, name_filter)
1144
1144
1145 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1145 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1146 """
1146 """
1147 Returns list of user group ids that user have access to based on given
1147 Returns list of user group ids that user have access to based on given
1148 perms. The cache flag should be only used in cases that are used for
1148 perms. The cache flag should be only used in cases that are used for
1149 display purposes, NOT IN ANY CASE for permission checks.
1149 display purposes, NOT IN ANY CASE for permission checks.
1150 """
1150 """
1151 from rhodecode.model.scm import UserGroupList
1151 from rhodecode.model.scm import UserGroupList
1152 if not perms:
1152 if not perms:
1153 perms = [
1153 perms = [
1154 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1154 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1155
1155
1156 def _cached_user_group_acl(user_id, perm_def, name_filter):
1156 def _cached_user_group_acl(user_id, perm_def, name_filter):
1157 qry = UserGroup.query()
1157 qry = UserGroup.query()
1158 if name_filter:
1158 if name_filter:
1159 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1159 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1160 qry = qry.filter(
1160 qry = qry.filter(
1161 UserGroup.users_group_name.ilike(ilike_expression))
1161 UserGroup.users_group_name.ilike(ilike_expression))
1162
1162
1163 return [x.users_group_id for x in
1163 return [x.users_group_id for x in
1164 UserGroupList(qry, perm_set=perm_def)]
1164 UserGroupList(qry, perm_set=perm_def)]
1165
1165
1166 compute = caches.conditional_cache(
1166 compute = caches.conditional_cache(
1167 'long_term', 'user_group_acl_ids',
1167 'long_term', 'user_group_acl_ids',
1168 condition=cache, func=_cached_user_group_acl)
1168 condition=cache, func=_cached_user_group_acl)
1169 return compute(self.user_id, perms, name_filter)
1169 return compute(self.user_id, perms, name_filter)
1170
1170
1171 @property
1171 @property
1172 def ip_allowed(self):
1172 def ip_allowed(self):
1173 """
1173 """
1174 Checks if ip_addr used in constructor is allowed from defined list of
1174 Checks if ip_addr used in constructor is allowed from defined list of
1175 allowed ip_addresses for user
1175 allowed ip_addresses for user
1176
1176
1177 :returns: boolean, True if ip is in allowed ip range
1177 :returns: boolean, True if ip is in allowed ip range
1178 """
1178 """
1179 # check IP
1179 # check IP
1180 inherit = self.inherit_default_permissions
1180 inherit = self.inherit_default_permissions
1181 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1181 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1182 inherit_from_default=inherit)
1182 inherit_from_default=inherit)
1183 @property
1183 @property
1184 def personal_repo_group(self):
1184 def personal_repo_group(self):
1185 return RepoGroup.get_user_personal_repo_group(self.user_id)
1185 return RepoGroup.get_user_personal_repo_group(self.user_id)
1186
1186
1187 @classmethod
1187 @classmethod
1188 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1188 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1189 allowed_ips = AuthUser.get_allowed_ips(
1189 allowed_ips = AuthUser.get_allowed_ips(
1190 user_id, cache=True, inherit_from_default=inherit_from_default)
1190 user_id, cache=True, inherit_from_default=inherit_from_default)
1191 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1191 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1192 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
1192 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
1193 return True
1193 return True
1194 else:
1194 else:
1195 log.info('Access for IP:%s forbidden, '
1195 log.info('Access for IP:%s forbidden, '
1196 'not in %s' % (ip_addr, allowed_ips))
1196 'not in %s' % (ip_addr, allowed_ips))
1197 return False
1197 return False
1198
1198
1199 def __repr__(self):
1199 def __repr__(self):
1200 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1200 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1201 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1201 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1202
1202
1203 def set_authenticated(self, authenticated=True):
1203 def set_authenticated(self, authenticated=True):
1204 if self.user_id != self.anonymous_user.user_id:
1204 if self.user_id != self.anonymous_user.user_id:
1205 self.is_authenticated = authenticated
1205 self.is_authenticated = authenticated
1206
1206
1207 def get_cookie_store(self):
1207 def get_cookie_store(self):
1208 return {
1208 return {
1209 'username': self.username,
1209 'username': self.username,
1210 'password': md5(self.password),
1210 'password': md5(self.password),
1211 'user_id': self.user_id,
1211 'user_id': self.user_id,
1212 'is_authenticated': self.is_authenticated
1212 'is_authenticated': self.is_authenticated
1213 }
1213 }
1214
1214
1215 @classmethod
1215 @classmethod
1216 def from_cookie_store(cls, cookie_store):
1216 def from_cookie_store(cls, cookie_store):
1217 """
1217 """
1218 Creates AuthUser from a cookie store
1218 Creates AuthUser from a cookie store
1219
1219
1220 :param cls:
1220 :param cls:
1221 :param cookie_store:
1221 :param cookie_store:
1222 """
1222 """
1223 user_id = cookie_store.get('user_id')
1223 user_id = cookie_store.get('user_id')
1224 username = cookie_store.get('username')
1224 username = cookie_store.get('username')
1225 api_key = cookie_store.get('api_key')
1225 api_key = cookie_store.get('api_key')
1226 return AuthUser(user_id, api_key, username)
1226 return AuthUser(user_id, api_key, username)
1227
1227
1228 @classmethod
1228 @classmethod
1229 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1229 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1230 _set = set()
1230 _set = set()
1231
1231
1232 if inherit_from_default:
1232 if inherit_from_default:
1233 default_ips = UserIpMap.query().filter(
1233 default_ips = UserIpMap.query().filter(
1234 UserIpMap.user == User.get_default_user(cache=True))
1234 UserIpMap.user == User.get_default_user(cache=True))
1235 if cache:
1235 if cache:
1236 default_ips = default_ips.options(
1236 default_ips = default_ips.options(
1237 FromCache("sql_cache_short", "get_user_ips_default"))
1237 FromCache("sql_cache_short", "get_user_ips_default"))
1238
1238
1239 # populate from default user
1239 # populate from default user
1240 for ip in default_ips:
1240 for ip in default_ips:
1241 try:
1241 try:
1242 _set.add(ip.ip_addr)
1242 _set.add(ip.ip_addr)
1243 except ObjectDeletedError:
1243 except ObjectDeletedError:
1244 # since we use heavy caching sometimes it happens that
1244 # since we use heavy caching sometimes it happens that
1245 # we get deleted objects here, we just skip them
1245 # we get deleted objects here, we just skip them
1246 pass
1246 pass
1247
1247
1248 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1248 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1249 if cache:
1249 if cache:
1250 user_ips = user_ips.options(
1250 user_ips = user_ips.options(
1251 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1251 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1252
1252
1253 for ip in user_ips:
1253 for ip in user_ips:
1254 try:
1254 try:
1255 _set.add(ip.ip_addr)
1255 _set.add(ip.ip_addr)
1256 except ObjectDeletedError:
1256 except ObjectDeletedError:
1257 # since we use heavy caching sometimes it happens that we get
1257 # since we use heavy caching sometimes it happens that we get
1258 # deleted objects here, we just skip them
1258 # deleted objects here, we just skip them
1259 pass
1259 pass
1260 return _set or set(['0.0.0.0/0', '::/0'])
1260 return _set or set(['0.0.0.0/0', '::/0'])
1261
1261
1262
1262
1263 def set_available_permissions(config):
1263 def set_available_permissions(config):
1264 """
1264 """
1265 This function will propagate pylons globals with all available defined
1265 This function will propagate pylons globals with all available defined
1266 permission given in db. We don't want to check each time from db for new
1266 permission given in db. We don't want to check each time from db for new
1267 permissions since adding a new permission also requires application restart
1267 permissions since adding a new permission also requires application restart
1268 ie. to decorate new views with the newly created permission
1268 ie. to decorate new views with the newly created permission
1269
1269
1270 :param config: current pylons config instance
1270 :param config: current pylons config instance
1271
1271
1272 """
1272 """
1273 log.info('getting information about all available permissions')
1273 log.info('getting information about all available permissions')
1274 try:
1274 try:
1275 sa = meta.Session
1275 sa = meta.Session
1276 all_perms = sa.query(Permission).all()
1276 all_perms = sa.query(Permission).all()
1277 config['available_permissions'] = [x.permission_name for x in all_perms]
1277 config['available_permissions'] = [x.permission_name for x in all_perms]
1278 except Exception:
1278 except Exception:
1279 log.error(traceback.format_exc())
1279 log.error(traceback.format_exc())
1280 finally:
1280 finally:
1281 meta.Session.remove()
1281 meta.Session.remove()
1282
1282
1283
1283
1284 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1284 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1285 """
1285 """
1286 Return the current authentication token, creating one if one doesn't
1286 Return the current authentication token, creating one if one doesn't
1287 already exist and the save_if_missing flag is present.
1287 already exist and the save_if_missing flag is present.
1288
1288
1289 :param session: pass in the pylons session, else we use the global ones
1289 :param session: pass in the pylons session, else we use the global ones
1290 :param force_new: force to re-generate the token and store it in session
1290 :param force_new: force to re-generate the token and store it in session
1291 :param save_if_missing: save the newly generated token if it's missing in
1291 :param save_if_missing: save the newly generated token if it's missing in
1292 session
1292 session
1293 """
1293 """
1294 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1294 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1295 # from pyramid.csrf import get_csrf_token
1295 # from pyramid.csrf import get_csrf_token
1296
1296
1297 if not session:
1297 if not session:
1298 from pylons import session
1298 from pylons import session
1299
1299
1300 if (csrf_token_key not in session and save_if_missing) or force_new:
1300 if (csrf_token_key not in session and save_if_missing) or force_new:
1301 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1301 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1302 session[csrf_token_key] = token
1302 session[csrf_token_key] = token
1303 if hasattr(session, 'save'):
1303 if hasattr(session, 'save'):
1304 session.save()
1304 session.save()
1305 return session.get(csrf_token_key)
1305 return session.get(csrf_token_key)
1306
1306
1307
1307
1308 def get_request(perm_class):
1308 def get_request(perm_class_instance):
1309 from pyramid.threadlocal import get_current_request
1309 from pyramid.threadlocal import get_current_request
1310 pyramid_request = get_current_request()
1310 pyramid_request = get_current_request()
1311 if not pyramid_request:
1311 if not pyramid_request:
1312 # return global request of pylons in case pyramid isn't available
1312 # return global request of pylons in case pyramid isn't available
1313 # NOTE(marcink): this should be removed after migration to pyramid
1313 # NOTE(marcink): this should be removed after migration to pyramid
1314 from pylons import request
1314 from pylons import request
1315 return request
1315 return request
1316 return pyramid_request
1316 return pyramid_request
1317
1317
1318
1318
1319 # CHECK DECORATORS
1319 # CHECK DECORATORS
1320 class CSRFRequired(object):
1320 class CSRFRequired(object):
1321 """
1321 """
1322 Decorator for authenticating a form
1322 Decorator for authenticating a form
1323
1323
1324 This decorator uses an authorization token stored in the client's
1324 This decorator uses an authorization token stored in the client's
1325 session for prevention of certain Cross-site request forgery (CSRF)
1325 session for prevention of certain Cross-site request forgery (CSRF)
1326 attacks (See
1326 attacks (See
1327 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1327 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1328 information).
1328 information).
1329
1329
1330 For use with the ``webhelpers.secure_form`` helper functions.
1330 For use with the ``webhelpers.secure_form`` helper functions.
1331
1331
1332 """
1332 """
1333 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1333 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1334 except_methods=None):
1334 except_methods=None):
1335 self.token = token
1335 self.token = token
1336 self.header = header
1336 self.header = header
1337 self.except_methods = except_methods or []
1337 self.except_methods = except_methods or []
1338
1338
1339 def __call__(self, func):
1339 def __call__(self, func):
1340 return get_cython_compat_decorator(self.__wrapper, func)
1340 return get_cython_compat_decorator(self.__wrapper, func)
1341
1341
1342 def _get_csrf(self, _request):
1342 def _get_csrf(self, _request):
1343 return _request.POST.get(self.token, _request.headers.get(self.header))
1343 return _request.POST.get(self.token, _request.headers.get(self.header))
1344
1344
1345 def check_csrf(self, _request, cur_token):
1345 def check_csrf(self, _request, cur_token):
1346 supplied_token = self._get_csrf(_request)
1346 supplied_token = self._get_csrf(_request)
1347 return supplied_token and supplied_token == cur_token
1347 return supplied_token and supplied_token == cur_token
1348
1348
1349 def _get_request(self):
1349 def _get_request(self):
1350 return get_request(self)
1350 return get_request(self)
1351
1351
1352 def __wrapper(self, func, *fargs, **fkwargs):
1352 def __wrapper(self, func, *fargs, **fkwargs):
1353 request = self._get_request()
1353 request = self._get_request()
1354
1354
1355 if request.method in self.except_methods:
1355 if request.method in self.except_methods:
1356 return func(*fargs, **fkwargs)
1356 return func(*fargs, **fkwargs)
1357
1357
1358 cur_token = get_csrf_token(save_if_missing=False)
1358 cur_token = get_csrf_token(save_if_missing=False)
1359 if self.check_csrf(request, cur_token):
1359 if self.check_csrf(request, cur_token):
1360 if request.POST.get(self.token):
1360 if request.POST.get(self.token):
1361 del request.POST[self.token]
1361 del request.POST[self.token]
1362 return func(*fargs, **fkwargs)
1362 return func(*fargs, **fkwargs)
1363 else:
1363 else:
1364 reason = 'token-missing'
1364 reason = 'token-missing'
1365 supplied_token = self._get_csrf(request)
1365 supplied_token = self._get_csrf(request)
1366 if supplied_token and cur_token != supplied_token:
1366 if supplied_token and cur_token != supplied_token:
1367 reason = 'token-mismatch [%s:%s]' % (
1367 reason = 'token-mismatch [%s:%s]' % (
1368 cur_token or ''[:6], supplied_token or ''[:6])
1368 cur_token or ''[:6], supplied_token or ''[:6])
1369
1369
1370 csrf_message = \
1370 csrf_message = \
1371 ("Cross-site request forgery detected, request denied. See "
1371 ("Cross-site request forgery detected, request denied. See "
1372 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1372 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1373 "more information.")
1373 "more information.")
1374 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1374 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1375 'REMOTE_ADDR:%s, HEADERS:%s' % (
1375 'REMOTE_ADDR:%s, HEADERS:%s' % (
1376 request, reason, request.remote_addr, request.headers))
1376 request, reason, request.remote_addr, request.headers))
1377
1377
1378 raise HTTPForbidden(explanation=csrf_message)
1378 raise HTTPForbidden(explanation=csrf_message)
1379
1379
1380
1380
1381 class LoginRequired(object):
1381 class LoginRequired(object):
1382 """
1382 """
1383 Must be logged in to execute this function else
1383 Must be logged in to execute this function else
1384 redirect to login page
1384 redirect to login page
1385
1385
1386 :param api_access: if enabled this checks only for valid auth token
1386 :param api_access: if enabled this checks only for valid auth token
1387 and grants access based on valid token
1387 and grants access based on valid token
1388 """
1388 """
1389 def __init__(self, auth_token_access=None):
1389 def __init__(self, auth_token_access=None):
1390 self.auth_token_access = auth_token_access
1390 self.auth_token_access = auth_token_access
1391
1391
1392 def __call__(self, func):
1392 def __call__(self, func):
1393 return get_cython_compat_decorator(self.__wrapper, func)
1393 return get_cython_compat_decorator(self.__wrapper, func)
1394
1394
1395 def _get_request(self):
1395 def _get_request(self):
1396 return get_request(self)
1396 return get_request(self)
1397
1397
1398 def __wrapper(self, func, *fargs, **fkwargs):
1398 def __wrapper(self, func, *fargs, **fkwargs):
1399 from rhodecode.lib import helpers as h
1399 from rhodecode.lib import helpers as h
1400 cls = fargs[0]
1400 cls = fargs[0]
1401 user = cls._rhodecode_user
1401 user = cls._rhodecode_user
1402 request = self._get_request()
1402 request = self._get_request()
1403
1403
1404 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1404 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1405 log.debug('Starting login restriction checks for user: %s' % (user,))
1405 log.debug('Starting login restriction checks for user: %s' % (user,))
1406 # check if our IP is allowed
1406 # check if our IP is allowed
1407 ip_access_valid = True
1407 ip_access_valid = True
1408 if not user.ip_allowed:
1408 if not user.ip_allowed:
1409 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1409 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1410 category='warning')
1410 category='warning')
1411 ip_access_valid = False
1411 ip_access_valid = False
1412
1412
1413 # check if we used an APIKEY and it's a valid one
1413 # check if we used an APIKEY and it's a valid one
1414 # defined white-list of controllers which API access will be enabled
1414 # defined white-list of controllers which API access will be enabled
1415 _auth_token = request.GET.get(
1415 _auth_token = request.GET.get(
1416 'auth_token', '') or request.GET.get('api_key', '')
1416 'auth_token', '') or request.GET.get('api_key', '')
1417 auth_token_access_valid = allowed_auth_token_access(
1417 auth_token_access_valid = allowed_auth_token_access(
1418 loc, auth_token=_auth_token)
1418 loc, auth_token=_auth_token)
1419
1419
1420 # explicit controller is enabled or API is in our whitelist
1420 # explicit controller is enabled or API is in our whitelist
1421 if self.auth_token_access or auth_token_access_valid:
1421 if self.auth_token_access or auth_token_access_valid:
1422 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1422 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1423 db_user = user.get_instance()
1423 db_user = user.get_instance()
1424
1424
1425 if db_user:
1425 if db_user:
1426 if self.auth_token_access:
1426 if self.auth_token_access:
1427 roles = self.auth_token_access
1427 roles = self.auth_token_access
1428 else:
1428 else:
1429 roles = [UserApiKeys.ROLE_HTTP]
1429 roles = [UserApiKeys.ROLE_HTTP]
1430 token_match = db_user.authenticate_by_token(
1430 token_match = db_user.authenticate_by_token(
1431 _auth_token, roles=roles)
1431 _auth_token, roles=roles)
1432 else:
1432 else:
1433 log.debug('Unable to fetch db instance for auth user: %s', user)
1433 log.debug('Unable to fetch db instance for auth user: %s', user)
1434 token_match = False
1434 token_match = False
1435
1435
1436 if _auth_token and token_match:
1436 if _auth_token and token_match:
1437 auth_token_access_valid = True
1437 auth_token_access_valid = True
1438 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1438 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1439 else:
1439 else:
1440 auth_token_access_valid = False
1440 auth_token_access_valid = False
1441 if not _auth_token:
1441 if not _auth_token:
1442 log.debug("AUTH TOKEN *NOT* present in request")
1442 log.debug("AUTH TOKEN *NOT* present in request")
1443 else:
1443 else:
1444 log.warning(
1444 log.warning(
1445 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1445 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1446
1446
1447 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1447 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1448 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1448 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1449 else 'AUTH_TOKEN_AUTH'
1449 else 'AUTH_TOKEN_AUTH'
1450
1450
1451 if ip_access_valid and (
1451 if ip_access_valid and (
1452 user.is_authenticated or auth_token_access_valid):
1452 user.is_authenticated or auth_token_access_valid):
1453 log.info(
1453 log.info(
1454 'user %s authenticating with:%s IS authenticated on func %s'
1454 'user %s authenticating with:%s IS authenticated on func %s'
1455 % (user, reason, loc))
1455 % (user, reason, loc))
1456
1456
1457 # update user data to check last activity
1457 # update user data to check last activity
1458 user.update_lastactivity()
1458 user.update_lastactivity()
1459 Session().commit()
1459 Session().commit()
1460 return func(*fargs, **fkwargs)
1460 return func(*fargs, **fkwargs)
1461 else:
1461 else:
1462 log.warning(
1462 log.warning(
1463 'user %s authenticating with:%s NOT authenticated on '
1463 'user %s authenticating with:%s NOT authenticated on '
1464 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1464 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1465 % (user, reason, loc, ip_access_valid,
1465 % (user, reason, loc, ip_access_valid,
1466 auth_token_access_valid))
1466 auth_token_access_valid))
1467 # we preserve the get PARAM
1467 # we preserve the get PARAM
1468 came_from = request.path_qs
1468 came_from = request.path_qs
1469 log.debug('redirecting to login page with %s' % (came_from,))
1469 log.debug('redirecting to login page with %s' % (came_from,))
1470 raise HTTPFound(
1470 raise HTTPFound(
1471 h.route_path('login', _query={'came_from': came_from}))
1471 h.route_path('login', _query={'came_from': came_from}))
1472
1472
1473
1473
1474 class NotAnonymous(object):
1474 class NotAnonymous(object):
1475 """
1475 """
1476 Must be logged in to execute this function else
1476 Must be logged in to execute this function else
1477 redirect to login page
1477 redirect to login page
1478 """
1478 """
1479
1479
1480 def __call__(self, func):
1480 def __call__(self, func):
1481 return get_cython_compat_decorator(self.__wrapper, func)
1481 return get_cython_compat_decorator(self.__wrapper, func)
1482
1482
1483 def _get_request(self):
1483 def _get_request(self):
1484 return get_request(self)
1484 return get_request(self)
1485
1485
1486 def __wrapper(self, func, *fargs, **fkwargs):
1486 def __wrapper(self, func, *fargs, **fkwargs):
1487 import rhodecode.lib.helpers as h
1487 import rhodecode.lib.helpers as h
1488 cls = fargs[0]
1488 cls = fargs[0]
1489 self.user = cls._rhodecode_user
1489 self.user = cls._rhodecode_user
1490 request = self._get_request()
1490 request = self._get_request()
1491
1491
1492 log.debug('Checking if user is not anonymous @%s' % cls)
1492 log.debug('Checking if user is not anonymous @%s' % cls)
1493
1493
1494 anonymous = self.user.username == User.DEFAULT_USER
1494 anonymous = self.user.username == User.DEFAULT_USER
1495
1495
1496 if anonymous:
1496 if anonymous:
1497 came_from = request.path_qs
1497 came_from = request.path_qs
1498 h.flash(_('You need to be a registered user to '
1498 h.flash(_('You need to be a registered user to '
1499 'perform this action'),
1499 'perform this action'),
1500 category='warning')
1500 category='warning')
1501 raise HTTPFound(
1501 raise HTTPFound(
1502 h.route_path('login', _query={'came_from': came_from}))
1502 h.route_path('login', _query={'came_from': came_from}))
1503 else:
1503 else:
1504 return func(*fargs, **fkwargs)
1504 return func(*fargs, **fkwargs)
1505
1505
1506
1506
1507 class PermsDecorator(object):
1507 class PermsDecorator(object):
1508 """
1508 """
1509 Base class for controller decorators, we extract the current user from
1509 Base class for controller decorators, we extract the current user from
1510 the class itself, which has it stored in base controllers
1510 the class itself, which has it stored in base controllers
1511 """
1511 """
1512
1512
1513 def __init__(self, *required_perms):
1513 def __init__(self, *required_perms):
1514 self.required_perms = set(required_perms)
1514 self.required_perms = set(required_perms)
1515
1515
1516 def __call__(self, func):
1516 def __call__(self, func):
1517 return get_cython_compat_decorator(self.__wrapper, func)
1517 return get_cython_compat_decorator(self.__wrapper, func)
1518
1518
1519 def _get_request(self):
1519 def _get_request(self):
1520 return get_request(self)
1520 return get_request(self)
1521
1521
1522 def _get_came_from(self):
1522 def _get_came_from(self):
1523 _request = self._get_request()
1523 _request = self._get_request()
1524
1524
1525 # both pylons/pyramid has this attribute
1525 # both pylons/pyramid has this attribute
1526 return _request.path_qs
1526 return _request.path_qs
1527
1527
1528 def __wrapper(self, func, *fargs, **fkwargs):
1528 def __wrapper(self, func, *fargs, **fkwargs):
1529 import rhodecode.lib.helpers as h
1529 import rhodecode.lib.helpers as h
1530 cls = fargs[0]
1530 cls = fargs[0]
1531 _user = cls._rhodecode_user
1531 _user = cls._rhodecode_user
1532
1532
1533 log.debug('checking %s permissions %s for %s %s',
1533 log.debug('checking %s permissions %s for %s %s',
1534 self.__class__.__name__, self.required_perms, cls, _user)
1534 self.__class__.__name__, self.required_perms, cls, _user)
1535
1535
1536 if self.check_permissions(_user):
1536 if self.check_permissions(_user):
1537 log.debug('Permission granted for %s %s', cls, _user)
1537 log.debug('Permission granted for %s %s', cls, _user)
1538 return func(*fargs, **fkwargs)
1538 return func(*fargs, **fkwargs)
1539
1539
1540 else:
1540 else:
1541 log.debug('Permission denied for %s %s', cls, _user)
1541 log.debug('Permission denied for %s %s', cls, _user)
1542 anonymous = _user.username == User.DEFAULT_USER
1542 anonymous = _user.username == User.DEFAULT_USER
1543
1543
1544 if anonymous:
1544 if anonymous:
1545 came_from = self._get_came_from()
1545 came_from = self._get_came_from()
1546 h.flash(_('You need to be signed in to view this page'),
1546 h.flash(_('You need to be signed in to view this page'),
1547 category='warning')
1547 category='warning')
1548 raise HTTPFound(
1548 raise HTTPFound(
1549 h.route_path('login', _query={'came_from': came_from}))
1549 h.route_path('login', _query={'came_from': came_from}))
1550
1550
1551 else:
1551 else:
1552 # redirect with 404 to prevent resource discovery
1552 # redirect with 404 to prevent resource discovery
1553 raise HTTPNotFound()
1553 raise HTTPNotFound()
1554
1554
1555 def check_permissions(self, user):
1555 def check_permissions(self, user):
1556 """Dummy function for overriding"""
1556 """Dummy function for overriding"""
1557 raise NotImplementedError(
1557 raise NotImplementedError(
1558 'You have to write this function in child class')
1558 'You have to write this function in child class')
1559
1559
1560
1560
1561 class HasPermissionAllDecorator(PermsDecorator):
1561 class HasPermissionAllDecorator(PermsDecorator):
1562 """
1562 """
1563 Checks for access permission for all given predicates. All of them
1563 Checks for access permission for all given predicates. All of them
1564 have to be meet in order to fulfill the request
1564 have to be meet in order to fulfill the request
1565 """
1565 """
1566
1566
1567 def check_permissions(self, user):
1567 def check_permissions(self, user):
1568 perms = user.permissions_with_scope({})
1568 perms = user.permissions_with_scope({})
1569 if self.required_perms.issubset(perms['global']):
1569 if self.required_perms.issubset(perms['global']):
1570 return True
1570 return True
1571 return False
1571 return False
1572
1572
1573
1573
1574 class HasPermissionAnyDecorator(PermsDecorator):
1574 class HasPermissionAnyDecorator(PermsDecorator):
1575 """
1575 """
1576 Checks for access permission for any of given predicates. In order to
1576 Checks for access permission for any of given predicates. In order to
1577 fulfill the request any of predicates must be meet
1577 fulfill the request any of predicates must be meet
1578 """
1578 """
1579
1579
1580 def check_permissions(self, user):
1580 def check_permissions(self, user):
1581 perms = user.permissions_with_scope({})
1581 perms = user.permissions_with_scope({})
1582 if self.required_perms.intersection(perms['global']):
1582 if self.required_perms.intersection(perms['global']):
1583 return True
1583 return True
1584 return False
1584 return False
1585
1585
1586
1586
1587 class HasRepoPermissionAllDecorator(PermsDecorator):
1587 class HasRepoPermissionAllDecorator(PermsDecorator):
1588 """
1588 """
1589 Checks for access permission for all given predicates for specific
1589 Checks for access permission for all given predicates for specific
1590 repository. All of them have to be meet in order to fulfill the request
1590 repository. All of them have to be meet in order to fulfill the request
1591 """
1591 """
1592 def _get_repo_name(self):
1592 def _get_repo_name(self):
1593 _request = self._get_request()
1593 _request = self._get_request()
1594 return get_repo_slug(_request)
1594 return get_repo_slug(_request)
1595
1595
1596 def check_permissions(self, user):
1596 def check_permissions(self, user):
1597 perms = user.permissions
1597 perms = user.permissions
1598 repo_name = self._get_repo_name()
1598 repo_name = self._get_repo_name()
1599
1599
1600 try:
1600 try:
1601 user_perms = set([perms['repositories'][repo_name]])
1601 user_perms = set([perms['repositories'][repo_name]])
1602 except KeyError:
1602 except KeyError:
1603 log.debug('cannot locate repo with name: `%s` in permissions defs',
1603 log.debug('cannot locate repo with name: `%s` in permissions defs',
1604 repo_name)
1604 repo_name)
1605 return False
1605 return False
1606
1606
1607 log.debug('checking `%s` permissions for repo `%s`',
1607 log.debug('checking `%s` permissions for repo `%s`',
1608 user_perms, repo_name)
1608 user_perms, repo_name)
1609 if self.required_perms.issubset(user_perms):
1609 if self.required_perms.issubset(user_perms):
1610 return True
1610 return True
1611 return False
1611 return False
1612
1612
1613
1613
1614 class HasRepoPermissionAnyDecorator(PermsDecorator):
1614 class HasRepoPermissionAnyDecorator(PermsDecorator):
1615 """
1615 """
1616 Checks for access permission for any of given predicates for specific
1616 Checks for access permission for any of given predicates for specific
1617 repository. In order to fulfill the request any of predicates must be meet
1617 repository. In order to fulfill the request any of predicates must be meet
1618 """
1618 """
1619 def _get_repo_name(self):
1619 def _get_repo_name(self):
1620 _request = self._get_request()
1620 _request = self._get_request()
1621 return get_repo_slug(_request)
1621 return get_repo_slug(_request)
1622
1622
1623 def check_permissions(self, user):
1623 def check_permissions(self, user):
1624 perms = user.permissions
1624 perms = user.permissions
1625 repo_name = self._get_repo_name()
1625 repo_name = self._get_repo_name()
1626
1626
1627 try:
1627 try:
1628 user_perms = set([perms['repositories'][repo_name]])
1628 user_perms = set([perms['repositories'][repo_name]])
1629 except KeyError:
1629 except KeyError:
1630 log.debug(
1630 log.debug(
1631 'cannot locate repo with name: `%s` in permissions defs',
1631 'cannot locate repo with name: `%s` in permissions defs',
1632 repo_name)
1632 repo_name)
1633 return False
1633 return False
1634
1634
1635 log.debug('checking `%s` permissions for repo `%s`',
1635 log.debug('checking `%s` permissions for repo `%s`',
1636 user_perms, repo_name)
1636 user_perms, repo_name)
1637 if self.required_perms.intersection(user_perms):
1637 if self.required_perms.intersection(user_perms):
1638 return True
1638 return True
1639 return False
1639 return False
1640
1640
1641
1641
1642 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1642 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1643 """
1643 """
1644 Checks for access permission for all given predicates for specific
1644 Checks for access permission for all given predicates for specific
1645 repository group. All of them have to be meet in order to
1645 repository group. All of them have to be meet in order to
1646 fulfill the request
1646 fulfill the request
1647 """
1647 """
1648 def _get_repo_group_name(self):
1648 def _get_repo_group_name(self):
1649 _request = self._get_request()
1649 _request = self._get_request()
1650 return get_repo_group_slug(_request)
1650 return get_repo_group_slug(_request)
1651
1651
1652 def check_permissions(self, user):
1652 def check_permissions(self, user):
1653 perms = user.permissions
1653 perms = user.permissions
1654 group_name = self._get_repo_group_name()
1654 group_name = self._get_repo_group_name()
1655 try:
1655 try:
1656 user_perms = set([perms['repositories_groups'][group_name]])
1656 user_perms = set([perms['repositories_groups'][group_name]])
1657 except KeyError:
1657 except KeyError:
1658 log.debug(
1658 log.debug(
1659 'cannot locate repo group with name: `%s` in permissions defs',
1659 'cannot locate repo group with name: `%s` in permissions defs',
1660 group_name)
1660 group_name)
1661 return False
1661 return False
1662
1662
1663 log.debug('checking `%s` permissions for repo group `%s`',
1663 log.debug('checking `%s` permissions for repo group `%s`',
1664 user_perms, group_name)
1664 user_perms, group_name)
1665 if self.required_perms.issubset(user_perms):
1665 if self.required_perms.issubset(user_perms):
1666 return True
1666 return True
1667 return False
1667 return False
1668
1668
1669
1669
1670 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1670 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1671 """
1671 """
1672 Checks for access permission for any of given predicates for specific
1672 Checks for access permission for any of given predicates for specific
1673 repository group. In order to fulfill the request any
1673 repository group. In order to fulfill the request any
1674 of predicates must be met
1674 of predicates must be met
1675 """
1675 """
1676 def _get_repo_group_name(self):
1676 def _get_repo_group_name(self):
1677 _request = self._get_request()
1677 _request = self._get_request()
1678 return get_repo_group_slug(_request)
1678 return get_repo_group_slug(_request)
1679
1679
1680 def check_permissions(self, user):
1680 def check_permissions(self, user):
1681 perms = user.permissions
1681 perms = user.permissions
1682 group_name = self._get_repo_group_name()
1682 group_name = self._get_repo_group_name()
1683
1683
1684 try:
1684 try:
1685 user_perms = set([perms['repositories_groups'][group_name]])
1685 user_perms = set([perms['repositories_groups'][group_name]])
1686 except KeyError:
1686 except KeyError:
1687 log.debug(
1687 log.debug(
1688 'cannot locate repo group with name: `%s` in permissions defs',
1688 'cannot locate repo group with name: `%s` in permissions defs',
1689 group_name)
1689 group_name)
1690 return False
1690 return False
1691
1691
1692 log.debug('checking `%s` permissions for repo group `%s`',
1692 log.debug('checking `%s` permissions for repo group `%s`',
1693 user_perms, group_name)
1693 user_perms, group_name)
1694 if self.required_perms.intersection(user_perms):
1694 if self.required_perms.intersection(user_perms):
1695 return True
1695 return True
1696 return False
1696 return False
1697
1697
1698
1698
1699 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1699 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1700 """
1700 """
1701 Checks for access permission for all given predicates for specific
1701 Checks for access permission for all given predicates for specific
1702 user group. All of them have to be meet in order to fulfill the request
1702 user group. All of them have to be meet in order to fulfill the request
1703 """
1703 """
1704 def _get_user_group_name(self):
1704 def _get_user_group_name(self):
1705 _request = self._get_request()
1705 _request = self._get_request()
1706 return get_user_group_slug(_request)
1706 return get_user_group_slug(_request)
1707
1707
1708 def check_permissions(self, user):
1708 def check_permissions(self, user):
1709 perms = user.permissions
1709 perms = user.permissions
1710 group_name = self._get_user_group_name()
1710 group_name = self._get_user_group_name()
1711 try:
1711 try:
1712 user_perms = set([perms['user_groups'][group_name]])
1712 user_perms = set([perms['user_groups'][group_name]])
1713 except KeyError:
1713 except KeyError:
1714 return False
1714 return False
1715
1715
1716 if self.required_perms.issubset(user_perms):
1716 if self.required_perms.issubset(user_perms):
1717 return True
1717 return True
1718 return False
1718 return False
1719
1719
1720
1720
1721 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1721 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1722 """
1722 """
1723 Checks for access permission for any of given predicates for specific
1723 Checks for access permission for any of given predicates for specific
1724 user group. In order to fulfill the request any of predicates must be meet
1724 user group. In order to fulfill the request any of predicates must be meet
1725 """
1725 """
1726 def _get_user_group_name(self):
1726 def _get_user_group_name(self):
1727 _request = self._get_request()
1727 _request = self._get_request()
1728 return get_user_group_slug(_request)
1728 return get_user_group_slug(_request)
1729
1729
1730 def check_permissions(self, user):
1730 def check_permissions(self, user):
1731 perms = user.permissions
1731 perms = user.permissions
1732 group_name = self._get_user_group_name()
1732 group_name = self._get_user_group_name()
1733 try:
1733 try:
1734 user_perms = set([perms['user_groups'][group_name]])
1734 user_perms = set([perms['user_groups'][group_name]])
1735 except KeyError:
1735 except KeyError:
1736 return False
1736 return False
1737
1737
1738 if self.required_perms.intersection(user_perms):
1738 if self.required_perms.intersection(user_perms):
1739 return True
1739 return True
1740 return False
1740 return False
1741
1741
1742
1742
1743 # CHECK FUNCTIONS
1743 # CHECK FUNCTIONS
1744 class PermsFunction(object):
1744 class PermsFunction(object):
1745 """Base function for other check functions"""
1745 """Base function for other check functions"""
1746
1746
1747 def __init__(self, *perms):
1747 def __init__(self, *perms):
1748 self.required_perms = set(perms)
1748 self.required_perms = set(perms)
1749 self.repo_name = None
1749 self.repo_name = None
1750 self.repo_group_name = None
1750 self.repo_group_name = None
1751 self.user_group_name = None
1751 self.user_group_name = None
1752
1752
1753 def __bool__(self):
1753 def __bool__(self):
1754 frame = inspect.currentframe()
1754 frame = inspect.currentframe()
1755 stack_trace = traceback.format_stack(frame)
1755 stack_trace = traceback.format_stack(frame)
1756 log.error('Checking bool value on a class instance of perm '
1756 log.error('Checking bool value on a class instance of perm '
1757 'function is not allowed: %s' % ''.join(stack_trace))
1757 'function is not allowed: %s' % ''.join(stack_trace))
1758 # rather than throwing errors, here we always return False so if by
1758 # rather than throwing errors, here we always return False so if by
1759 # accident someone checks truth for just an instance it will always end
1759 # accident someone checks truth for just an instance it will always end
1760 # up in returning False
1760 # up in returning False
1761 return False
1761 return False
1762 __nonzero__ = __bool__
1762 __nonzero__ = __bool__
1763
1763
1764 def __call__(self, check_location='', user=None):
1764 def __call__(self, check_location='', user=None):
1765 if not user:
1765 if not user:
1766 log.debug('Using user attribute from global request')
1766 log.debug('Using user attribute from global request')
1767 # TODO: remove this someday,put as user as attribute here
1767 # TODO: remove this someday,put as user as attribute here
1768 request = self._get_request()
1768 request = self._get_request()
1769 user = request.user
1769 user = request.user
1770
1770
1771 # init auth user if not already given
1771 # init auth user if not already given
1772 if not isinstance(user, AuthUser):
1772 if not isinstance(user, AuthUser):
1773 log.debug('Wrapping user %s into AuthUser', user)
1773 log.debug('Wrapping user %s into AuthUser', user)
1774 user = AuthUser(user.user_id)
1774 user = AuthUser(user.user_id)
1775
1775
1776 cls_name = self.__class__.__name__
1776 cls_name = self.__class__.__name__
1777 check_scope = self._get_check_scope(cls_name)
1777 check_scope = self._get_check_scope(cls_name)
1778 check_location = check_location or 'unspecified location'
1778 check_location = check_location or 'unspecified location'
1779
1779
1780 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1780 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1781 self.required_perms, user, check_scope, check_location)
1781 self.required_perms, user, check_scope, check_location)
1782 if not user:
1782 if not user:
1783 log.warning('Empty user given for permission check')
1783 log.warning('Empty user given for permission check')
1784 return False
1784 return False
1785
1785
1786 if self.check_permissions(user):
1786 if self.check_permissions(user):
1787 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1787 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1788 check_scope, user, check_location)
1788 check_scope, user, check_location)
1789 return True
1789 return True
1790
1790
1791 else:
1791 else:
1792 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1792 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1793 check_scope, user, check_location)
1793 check_scope, user, check_location)
1794 return False
1794 return False
1795
1795
1796 def _get_request(self):
1796 def _get_request(self):
1797 return get_request(self)
1797 return get_request(self)
1798
1798
1799 def _get_check_scope(self, cls_name):
1799 def _get_check_scope(self, cls_name):
1800 return {
1800 return {
1801 'HasPermissionAll': 'GLOBAL',
1801 'HasPermissionAll': 'GLOBAL',
1802 'HasPermissionAny': 'GLOBAL',
1802 'HasPermissionAny': 'GLOBAL',
1803 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1803 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1804 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1804 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1805 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1805 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1806 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1806 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1807 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1807 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1808 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1808 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1809 }.get(cls_name, '?:%s' % cls_name)
1809 }.get(cls_name, '?:%s' % cls_name)
1810
1810
1811 def check_permissions(self, user):
1811 def check_permissions(self, user):
1812 """Dummy function for overriding"""
1812 """Dummy function for overriding"""
1813 raise Exception('You have to write this function in child class')
1813 raise Exception('You have to write this function in child class')
1814
1814
1815
1815
1816 class HasPermissionAll(PermsFunction):
1816 class HasPermissionAll(PermsFunction):
1817 def check_permissions(self, user):
1817 def check_permissions(self, user):
1818 perms = user.permissions_with_scope({})
1818 perms = user.permissions_with_scope({})
1819 if self.required_perms.issubset(perms.get('global')):
1819 if self.required_perms.issubset(perms.get('global')):
1820 return True
1820 return True
1821 return False
1821 return False
1822
1822
1823
1823
1824 class HasPermissionAny(PermsFunction):
1824 class HasPermissionAny(PermsFunction):
1825 def check_permissions(self, user):
1825 def check_permissions(self, user):
1826 perms = user.permissions_with_scope({})
1826 perms = user.permissions_with_scope({})
1827 if self.required_perms.intersection(perms.get('global')):
1827 if self.required_perms.intersection(perms.get('global')):
1828 return True
1828 return True
1829 return False
1829 return False
1830
1830
1831
1831
1832 class HasRepoPermissionAll(PermsFunction):
1832 class HasRepoPermissionAll(PermsFunction):
1833 def __call__(self, repo_name=None, check_location='', user=None):
1833 def __call__(self, repo_name=None, check_location='', user=None):
1834 self.repo_name = repo_name
1834 self.repo_name = repo_name
1835 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1835 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1836
1836
1837 def _get_repo_name(self):
1837 def _get_repo_name(self):
1838 if not self.repo_name:
1838 if not self.repo_name:
1839 _request = self._get_request()
1839 _request = self._get_request()
1840 self.repo_name = get_repo_slug(_request)
1840 self.repo_name = get_repo_slug(_request)
1841 return self.repo_name
1841 return self.repo_name
1842
1842
1843 def check_permissions(self, user):
1843 def check_permissions(self, user):
1844 self.repo_name = self._get_repo_name()
1844 self.repo_name = self._get_repo_name()
1845 perms = user.permissions
1845 perms = user.permissions
1846 try:
1846 try:
1847 user_perms = set([perms['repositories'][self.repo_name]])
1847 user_perms = set([perms['repositories'][self.repo_name]])
1848 except KeyError:
1848 except KeyError:
1849 return False
1849 return False
1850 if self.required_perms.issubset(user_perms):
1850 if self.required_perms.issubset(user_perms):
1851 return True
1851 return True
1852 return False
1852 return False
1853
1853
1854
1854
1855 class HasRepoPermissionAny(PermsFunction):
1855 class HasRepoPermissionAny(PermsFunction):
1856 def __call__(self, repo_name=None, check_location='', user=None):
1856 def __call__(self, repo_name=None, check_location='', user=None):
1857 self.repo_name = repo_name
1857 self.repo_name = repo_name
1858 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1858 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1859
1859
1860 def _get_repo_name(self):
1860 def _get_repo_name(self):
1861 if not self.repo_name:
1861 if not self.repo_name:
1862 _request = self._get_request()
1862 _request = self._get_request()
1863 self.repo_name = get_repo_slug(_request)
1863 self.repo_name = get_repo_slug(_request)
1864 return self.repo_name
1864 return self.repo_name
1865
1865
1866 def check_permissions(self, user):
1866 def check_permissions(self, user):
1867 self.repo_name = self._get_repo_name()
1867 self.repo_name = self._get_repo_name()
1868 perms = user.permissions
1868 perms = user.permissions
1869 try:
1869 try:
1870 user_perms = set([perms['repositories'][self.repo_name]])
1870 user_perms = set([perms['repositories'][self.repo_name]])
1871 except KeyError:
1871 except KeyError:
1872 return False
1872 return False
1873 if self.required_perms.intersection(user_perms):
1873 if self.required_perms.intersection(user_perms):
1874 return True
1874 return True
1875 return False
1875 return False
1876
1876
1877
1877
1878 class HasRepoGroupPermissionAny(PermsFunction):
1878 class HasRepoGroupPermissionAny(PermsFunction):
1879 def __call__(self, group_name=None, check_location='', user=None):
1879 def __call__(self, group_name=None, check_location='', user=None):
1880 self.repo_group_name = group_name
1880 self.repo_group_name = group_name
1881 return super(HasRepoGroupPermissionAny, self).__call__(
1881 return super(HasRepoGroupPermissionAny, self).__call__(
1882 check_location, user)
1882 check_location, user)
1883
1883
1884 def check_permissions(self, user):
1884 def check_permissions(self, user):
1885 perms = user.permissions
1885 perms = user.permissions
1886 try:
1886 try:
1887 user_perms = set(
1887 user_perms = set(
1888 [perms['repositories_groups'][self.repo_group_name]])
1888 [perms['repositories_groups'][self.repo_group_name]])
1889 except KeyError:
1889 except KeyError:
1890 return False
1890 return False
1891 if self.required_perms.intersection(user_perms):
1891 if self.required_perms.intersection(user_perms):
1892 return True
1892 return True
1893 return False
1893 return False
1894
1894
1895
1895
1896 class HasRepoGroupPermissionAll(PermsFunction):
1896 class HasRepoGroupPermissionAll(PermsFunction):
1897 def __call__(self, group_name=None, check_location='', user=None):
1897 def __call__(self, group_name=None, check_location='', user=None):
1898 self.repo_group_name = group_name
1898 self.repo_group_name = group_name
1899 return super(HasRepoGroupPermissionAll, self).__call__(
1899 return super(HasRepoGroupPermissionAll, self).__call__(
1900 check_location, user)
1900 check_location, user)
1901
1901
1902 def check_permissions(self, user):
1902 def check_permissions(self, user):
1903 perms = user.permissions
1903 perms = user.permissions
1904 try:
1904 try:
1905 user_perms = set(
1905 user_perms = set(
1906 [perms['repositories_groups'][self.repo_group_name]])
1906 [perms['repositories_groups'][self.repo_group_name]])
1907 except KeyError:
1907 except KeyError:
1908 return False
1908 return False
1909 if self.required_perms.issubset(user_perms):
1909 if self.required_perms.issubset(user_perms):
1910 return True
1910 return True
1911 return False
1911 return False
1912
1912
1913
1913
1914 class HasUserGroupPermissionAny(PermsFunction):
1914 class HasUserGroupPermissionAny(PermsFunction):
1915 def __call__(self, user_group_name=None, check_location='', user=None):
1915 def __call__(self, user_group_name=None, check_location='', user=None):
1916 self.user_group_name = user_group_name
1916 self.user_group_name = user_group_name
1917 return super(HasUserGroupPermissionAny, self).__call__(
1917 return super(HasUserGroupPermissionAny, self).__call__(
1918 check_location, user)
1918 check_location, user)
1919
1919
1920 def check_permissions(self, user):
1920 def check_permissions(self, user):
1921 perms = user.permissions
1921 perms = user.permissions
1922 try:
1922 try:
1923 user_perms = set([perms['user_groups'][self.user_group_name]])
1923 user_perms = set([perms['user_groups'][self.user_group_name]])
1924 except KeyError:
1924 except KeyError:
1925 return False
1925 return False
1926 if self.required_perms.intersection(user_perms):
1926 if self.required_perms.intersection(user_perms):
1927 return True
1927 return True
1928 return False
1928 return False
1929
1929
1930
1930
1931 class HasUserGroupPermissionAll(PermsFunction):
1931 class HasUserGroupPermissionAll(PermsFunction):
1932 def __call__(self, user_group_name=None, check_location='', user=None):
1932 def __call__(self, user_group_name=None, check_location='', user=None):
1933 self.user_group_name = user_group_name
1933 self.user_group_name = user_group_name
1934 return super(HasUserGroupPermissionAll, self).__call__(
1934 return super(HasUserGroupPermissionAll, self).__call__(
1935 check_location, user)
1935 check_location, user)
1936
1936
1937 def check_permissions(self, user):
1937 def check_permissions(self, user):
1938 perms = user.permissions
1938 perms = user.permissions
1939 try:
1939 try:
1940 user_perms = set([perms['user_groups'][self.user_group_name]])
1940 user_perms = set([perms['user_groups'][self.user_group_name]])
1941 except KeyError:
1941 except KeyError:
1942 return False
1942 return False
1943 if self.required_perms.issubset(user_perms):
1943 if self.required_perms.issubset(user_perms):
1944 return True
1944 return True
1945 return False
1945 return False
1946
1946
1947
1947
1948 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1948 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1949 class HasPermissionAnyMiddleware(object):
1949 class HasPermissionAnyMiddleware(object):
1950 def __init__(self, *perms):
1950 def __init__(self, *perms):
1951 self.required_perms = set(perms)
1951 self.required_perms = set(perms)
1952
1952
1953 def __call__(self, user, repo_name):
1953 def __call__(self, user, repo_name):
1954 # repo_name MUST be unicode, since we handle keys in permission
1954 # repo_name MUST be unicode, since we handle keys in permission
1955 # dict by unicode
1955 # dict by unicode
1956 repo_name = safe_unicode(repo_name)
1956 repo_name = safe_unicode(repo_name)
1957 user = AuthUser(user.user_id)
1957 user = AuthUser(user.user_id)
1958 log.debug(
1958 log.debug(
1959 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1959 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1960 self.required_perms, user, repo_name)
1960 self.required_perms, user, repo_name)
1961
1961
1962 if self.check_permissions(user, repo_name):
1962 if self.check_permissions(user, repo_name):
1963 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1963 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1964 repo_name, user, 'PermissionMiddleware')
1964 repo_name, user, 'PermissionMiddleware')
1965 return True
1965 return True
1966
1966
1967 else:
1967 else:
1968 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1968 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1969 repo_name, user, 'PermissionMiddleware')
1969 repo_name, user, 'PermissionMiddleware')
1970 return False
1970 return False
1971
1971
1972 def check_permissions(self, user, repo_name):
1972 def check_permissions(self, user, repo_name):
1973 perms = user.permissions_with_scope({'repo_name': repo_name})
1973 perms = user.permissions_with_scope({'repo_name': repo_name})
1974
1974
1975 try:
1975 try:
1976 user_perms = set([perms['repositories'][repo_name]])
1976 user_perms = set([perms['repositories'][repo_name]])
1977 except Exception:
1977 except Exception:
1978 log.exception('Error while accessing user permissions')
1978 log.exception('Error while accessing user permissions')
1979 return False
1979 return False
1980
1980
1981 if self.required_perms.intersection(user_perms):
1981 if self.required_perms.intersection(user_perms):
1982 return True
1982 return True
1983 return False
1983 return False
1984
1984
1985
1985
1986 # SPECIAL VERSION TO HANDLE API AUTH
1986 # SPECIAL VERSION TO HANDLE API AUTH
1987 class _BaseApiPerm(object):
1987 class _BaseApiPerm(object):
1988 def __init__(self, *perms):
1988 def __init__(self, *perms):
1989 self.required_perms = set(perms)
1989 self.required_perms = set(perms)
1990
1990
1991 def __call__(self, check_location=None, user=None, repo_name=None,
1991 def __call__(self, check_location=None, user=None, repo_name=None,
1992 group_name=None, user_group_name=None):
1992 group_name=None, user_group_name=None):
1993 cls_name = self.__class__.__name__
1993 cls_name = self.__class__.__name__
1994 check_scope = 'global:%s' % (self.required_perms,)
1994 check_scope = 'global:%s' % (self.required_perms,)
1995 if repo_name:
1995 if repo_name:
1996 check_scope += ', repo_name:%s' % (repo_name,)
1996 check_scope += ', repo_name:%s' % (repo_name,)
1997
1997
1998 if group_name:
1998 if group_name:
1999 check_scope += ', repo_group_name:%s' % (group_name,)
1999 check_scope += ', repo_group_name:%s' % (group_name,)
2000
2000
2001 if user_group_name:
2001 if user_group_name:
2002 check_scope += ', user_group_name:%s' % (user_group_name,)
2002 check_scope += ', user_group_name:%s' % (user_group_name,)
2003
2003
2004 log.debug(
2004 log.debug(
2005 'checking cls:%s %s %s @ %s'
2005 'checking cls:%s %s %s @ %s'
2006 % (cls_name, self.required_perms, check_scope, check_location))
2006 % (cls_name, self.required_perms, check_scope, check_location))
2007 if not user:
2007 if not user:
2008 log.debug('Empty User passed into arguments')
2008 log.debug('Empty User passed into arguments')
2009 return False
2009 return False
2010
2010
2011 # process user
2011 # process user
2012 if not isinstance(user, AuthUser):
2012 if not isinstance(user, AuthUser):
2013 user = AuthUser(user.user_id)
2013 user = AuthUser(user.user_id)
2014 if not check_location:
2014 if not check_location:
2015 check_location = 'unspecified'
2015 check_location = 'unspecified'
2016 if self.check_permissions(user.permissions, repo_name, group_name,
2016 if self.check_permissions(user.permissions, repo_name, group_name,
2017 user_group_name):
2017 user_group_name):
2018 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2018 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2019 check_scope, user, check_location)
2019 check_scope, user, check_location)
2020 return True
2020 return True
2021
2021
2022 else:
2022 else:
2023 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2023 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2024 check_scope, user, check_location)
2024 check_scope, user, check_location)
2025 return False
2025 return False
2026
2026
2027 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2027 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2028 user_group_name=None):
2028 user_group_name=None):
2029 """
2029 """
2030 implement in child class should return True if permissions are ok,
2030 implement in child class should return True if permissions are ok,
2031 False otherwise
2031 False otherwise
2032
2032
2033 :param perm_defs: dict with permission definitions
2033 :param perm_defs: dict with permission definitions
2034 :param repo_name: repo name
2034 :param repo_name: repo name
2035 """
2035 """
2036 raise NotImplementedError()
2036 raise NotImplementedError()
2037
2037
2038
2038
2039 class HasPermissionAllApi(_BaseApiPerm):
2039 class HasPermissionAllApi(_BaseApiPerm):
2040 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2040 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2041 user_group_name=None):
2041 user_group_name=None):
2042 if self.required_perms.issubset(perm_defs.get('global')):
2042 if self.required_perms.issubset(perm_defs.get('global')):
2043 return True
2043 return True
2044 return False
2044 return False
2045
2045
2046
2046
2047 class HasPermissionAnyApi(_BaseApiPerm):
2047 class HasPermissionAnyApi(_BaseApiPerm):
2048 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2048 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2049 user_group_name=None):
2049 user_group_name=None):
2050 if self.required_perms.intersection(perm_defs.get('global')):
2050 if self.required_perms.intersection(perm_defs.get('global')):
2051 return True
2051 return True
2052 return False
2052 return False
2053
2053
2054
2054
2055 class HasRepoPermissionAllApi(_BaseApiPerm):
2055 class HasRepoPermissionAllApi(_BaseApiPerm):
2056 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2056 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2057 user_group_name=None):
2057 user_group_name=None):
2058 try:
2058 try:
2059 _user_perms = set([perm_defs['repositories'][repo_name]])
2059 _user_perms = set([perm_defs['repositories'][repo_name]])
2060 except KeyError:
2060 except KeyError:
2061 log.warning(traceback.format_exc())
2061 log.warning(traceback.format_exc())
2062 return False
2062 return False
2063 if self.required_perms.issubset(_user_perms):
2063 if self.required_perms.issubset(_user_perms):
2064 return True
2064 return True
2065 return False
2065 return False
2066
2066
2067
2067
2068 class HasRepoPermissionAnyApi(_BaseApiPerm):
2068 class HasRepoPermissionAnyApi(_BaseApiPerm):
2069 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2069 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2070 user_group_name=None):
2070 user_group_name=None):
2071 try:
2071 try:
2072 _user_perms = set([perm_defs['repositories'][repo_name]])
2072 _user_perms = set([perm_defs['repositories'][repo_name]])
2073 except KeyError:
2073 except KeyError:
2074 log.warning(traceback.format_exc())
2074 log.warning(traceback.format_exc())
2075 return False
2075 return False
2076 if self.required_perms.intersection(_user_perms):
2076 if self.required_perms.intersection(_user_perms):
2077 return True
2077 return True
2078 return False
2078 return False
2079
2079
2080
2080
2081 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2081 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2082 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2082 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2083 user_group_name=None):
2083 user_group_name=None):
2084 try:
2084 try:
2085 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2085 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2086 except KeyError:
2086 except KeyError:
2087 log.warning(traceback.format_exc())
2087 log.warning(traceback.format_exc())
2088 return False
2088 return False
2089 if self.required_perms.intersection(_user_perms):
2089 if self.required_perms.intersection(_user_perms):
2090 return True
2090 return True
2091 return False
2091 return False
2092
2092
2093
2093
2094 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2094 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2095 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2095 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2096 user_group_name=None):
2096 user_group_name=None):
2097 try:
2097 try:
2098 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2098 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2099 except KeyError:
2099 except KeyError:
2100 log.warning(traceback.format_exc())
2100 log.warning(traceback.format_exc())
2101 return False
2101 return False
2102 if self.required_perms.issubset(_user_perms):
2102 if self.required_perms.issubset(_user_perms):
2103 return True
2103 return True
2104 return False
2104 return False
2105
2105
2106
2106
2107 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2107 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2108 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2108 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2109 user_group_name=None):
2109 user_group_name=None):
2110 try:
2110 try:
2111 _user_perms = set([perm_defs['user_groups'][user_group_name]])
2111 _user_perms = set([perm_defs['user_groups'][user_group_name]])
2112 except KeyError:
2112 except KeyError:
2113 log.warning(traceback.format_exc())
2113 log.warning(traceback.format_exc())
2114 return False
2114 return False
2115 if self.required_perms.intersection(_user_perms):
2115 if self.required_perms.intersection(_user_perms):
2116 return True
2116 return True
2117 return False
2117 return False
2118
2118
2119
2119
2120 def check_ip_access(source_ip, allowed_ips=None):
2120 def check_ip_access(source_ip, allowed_ips=None):
2121 """
2121 """
2122 Checks if source_ip is a subnet of any of allowed_ips.
2122 Checks if source_ip is a subnet of any of allowed_ips.
2123
2123
2124 :param source_ip:
2124 :param source_ip:
2125 :param allowed_ips: list of allowed ips together with mask
2125 :param allowed_ips: list of allowed ips together with mask
2126 """
2126 """
2127 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2127 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2128 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2128 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2129 if isinstance(allowed_ips, (tuple, list, set)):
2129 if isinstance(allowed_ips, (tuple, list, set)):
2130 for ip in allowed_ips:
2130 for ip in allowed_ips:
2131 ip = safe_unicode(ip)
2131 ip = safe_unicode(ip)
2132 try:
2132 try:
2133 network_address = ipaddress.ip_network(ip, strict=False)
2133 network_address = ipaddress.ip_network(ip, strict=False)
2134 if source_ip_address in network_address:
2134 if source_ip_address in network_address:
2135 log.debug('IP %s is network %s' %
2135 log.debug('IP %s is network %s' %
2136 (source_ip_address, network_address))
2136 (source_ip_address, network_address))
2137 return True
2137 return True
2138 # for any case we cannot determine the IP, don't crash just
2138 # for any case we cannot determine the IP, don't crash just
2139 # skip it and log as error, we want to say forbidden still when
2139 # skip it and log as error, we want to say forbidden still when
2140 # sending bad IP
2140 # sending bad IP
2141 except Exception:
2141 except Exception:
2142 log.error(traceback.format_exc())
2142 log.error(traceback.format_exc())
2143 continue
2143 continue
2144 return False
2144 return False
2145
2145
2146
2146
2147 def get_cython_compat_decorator(wrapper, func):
2147 def get_cython_compat_decorator(wrapper, func):
2148 """
2148 """
2149 Creates a cython compatible decorator. The previously used
2149 Creates a cython compatible decorator. The previously used
2150 decorator.decorator() function seems to be incompatible with cython.
2150 decorator.decorator() function seems to be incompatible with cython.
2151
2151
2152 :param wrapper: __wrapper method of the decorator class
2152 :param wrapper: __wrapper method of the decorator class
2153 :param func: decorated function
2153 :param func: decorated function
2154 """
2154 """
2155 @wraps(func)
2155 @wraps(func)
2156 def local_wrapper(*args, **kwds):
2156 def local_wrapper(*args, **kwds):
2157 return wrapper(func, *args, **kwds)
2157 return wrapper(func, *args, **kwds)
2158 local_wrapper.__wrapped__ = func
2158 local_wrapper.__wrapped__ = func
2159 return local_wrapper
2159 return local_wrapper
2160
2160
2161
2161
@@ -1,1054 +1,1053 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23 import datetime
24 import traceback
24 import traceback
25 from datetime import date
25 from datetime import date
26
26
27 from sqlalchemy import *
27 from sqlalchemy import *
28 from sqlalchemy.ext.hybrid import hybrid_property
28 from sqlalchemy.ext.hybrid import hybrid_property
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 from beaker.cache import cache_region, region_invalidate
30 from beaker.cache import cache_region, region_invalidate
31
31
32 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs.utils.helpers import get_scm
33 from rhodecode.lib.vcs.utils.helpers import get_scm
34 from rhodecode.lib.vcs.exceptions import VCSError
34 from rhodecode.lib.vcs.exceptions import VCSError
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 from rhodecode.lib.auth import generate_auth_token
36 from rhodecode.lib.auth import generate_auth_token
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
38 from rhodecode.lib.exceptions import UserGroupAssignedException
38 from rhodecode.lib.exceptions import UserGroupAssignedException
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40
40
41 from rhodecode.model.meta import Base, Session
41 from rhodecode.model.meta import Base, Session
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 #==============================================================================
47 #==============================================================================
48 # BASE CLASSES
48 # BASE CLASSES
49 #==============================================================================
49 #==============================================================================
50
50
51 class ModelSerializer(json.JSONEncoder):
51 class ModelSerializer(json.JSONEncoder):
52 """
52 """
53 Simple Serializer for JSON,
53 Simple Serializer for JSON,
54
54
55 usage::
55 usage::
56
56
57 to make object customized for serialization implement a __json__
57 to make object customized for serialization implement a __json__
58 method that will return a dict for serialization into json
58 method that will return a dict for serialization into json
59
59
60 example::
60 example::
61
61
62 class Task(object):
62 class Task(object):
63
63
64 def __init__(self, name, value):
64 def __init__(self, name, value):
65 self.name = name
65 self.name = name
66 self.value = value
66 self.value = value
67
67
68 def __json__(self):
68 def __json__(self):
69 return dict(name=self.name,
69 return dict(name=self.name,
70 value=self.value)
70 value=self.value)
71
71
72 """
72 """
73
73
74 def default(self, obj):
74 def default(self, obj):
75
75
76 if hasattr(obj, '__json__'):
76 if hasattr(obj, '__json__'):
77 return obj.__json__()
77 return obj.__json__()
78 else:
78 else:
79 return json.JSONEncoder.default(self, obj)
79 return json.JSONEncoder.default(self, obj)
80
80
81 class BaseModel(object):
81 class BaseModel(object):
82 """Base Model for all classess
82 """Base Model for all classess
83
83
84 """
84 """
85
85
86 @classmethod
86 @classmethod
87 def _get_keys(cls):
87 def _get_keys(cls):
88 """return column names for this model """
88 """return column names for this model """
89 return class_mapper(cls).c.keys()
89 return class_mapper(cls).c.keys()
90
90
91 def get_dict(self):
91 def get_dict(self):
92 """return dict with keys and values corresponding
92 """return dict with keys and values corresponding
93 to this model data """
93 to this model data """
94
94
95 d = {}
95 d = {}
96 for k in self._get_keys():
96 for k in self._get_keys():
97 d[k] = getattr(self, k)
97 d[k] = getattr(self, k)
98 return d
98 return d
99
99
100 def get_appstruct(self):
100 def get_appstruct(self):
101 """return list with keys and values tupples corresponding
101 """return list with keys and values tupples corresponding
102 to this model data """
102 to this model data """
103
103
104 l = []
104 l = []
105 for k in self._get_keys():
105 for k in self._get_keys():
106 l.append((k, getattr(self, k),))
106 l.append((k, getattr(self, k),))
107 return l
107 return l
108
108
109 def populate_obj(self, populate_dict):
109 def populate_obj(self, populate_dict):
110 """populate model with data from given populate_dict"""
110 """populate model with data from given populate_dict"""
111
111
112 for k in self._get_keys():
112 for k in self._get_keys():
113 if k in populate_dict:
113 if k in populate_dict:
114 setattr(self, k, populate_dict[k])
114 setattr(self, k, populate_dict[k])
115
115
116 @classmethod
116 @classmethod
117 def query(cls):
117 def query(cls):
118 return Session.query(cls)
118 return Session.query(cls)
119
119
120 @classmethod
120 @classmethod
121 def get(cls, id_):
121 def get(cls, id_):
122 if id_:
122 if id_:
123 return cls.query().get(id_)
123 return cls.query().get(id_)
124
124
125 @classmethod
125 @classmethod
126 def getAll(cls):
126 def getAll(cls):
127 return cls.query().all()
127 return cls.query().all()
128
128
129 @classmethod
129 @classmethod
130 def delete(cls, id_):
130 def delete(cls, id_):
131 obj = cls.query().get(id_)
131 obj = cls.query().get(id_)
132 Session.delete(obj)
132 Session.delete(obj)
133 Session.commit()
133 Session.commit()
134
134
135
135
136 class RhodeCodeSetting(Base, BaseModel):
136 class RhodeCodeSetting(Base, BaseModel):
137 __tablename__ = 'rhodecode_settings'
137 __tablename__ = 'rhodecode_settings'
138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
140 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
141 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
141 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
142
142
143 def __init__(self, k='', v=''):
143 def __init__(self, k='', v=''):
144 self.app_settings_name = k
144 self.app_settings_name = k
145 self.app_settings_value = v
145 self.app_settings_value = v
146
146
147
147
148 @validates('_app_settings_value')
148 @validates('_app_settings_value')
149 def validate_settings_value(self, key, val):
149 def validate_settings_value(self, key, val):
150 assert type(val) == unicode
150 assert type(val) == unicode
151 return val
151 return val
152
152
153 @hybrid_property
153 @hybrid_property
154 def app_settings_value(self):
154 def app_settings_value(self):
155 v = self._app_settings_value
155 v = self._app_settings_value
156 if v == 'ldap_active':
156 if v == 'ldap_active':
157 v = str2bool(v)
157 v = str2bool(v)
158 return v
158 return v
159
159
160 @app_settings_value.setter
160 @app_settings_value.setter
161 def app_settings_value(self, val):
161 def app_settings_value(self, val):
162 """
162 """
163 Setter that will always make sure we use unicode in app_settings_value
163 Setter that will always make sure we use unicode in app_settings_value
164
164
165 :param val:
165 :param val:
166 """
166 """
167 self._app_settings_value = safe_unicode(val)
167 self._app_settings_value = safe_unicode(val)
168
168
169 def __repr__(self):
169 def __repr__(self):
170 return "<%s('%s:%s')>" % (self.__class__.__name__,
170 return "<%s('%s:%s')>" % (self.__class__.__name__,
171 self.app_settings_name, self.app_settings_value)
171 self.app_settings_name, self.app_settings_value)
172
172
173
173
174 @classmethod
174 @classmethod
175 def get_by_name(cls, ldap_key):
175 def get_by_name(cls, ldap_key):
176 return cls.query()\
176 return cls.query()\
177 .filter(cls.app_settings_name == ldap_key).scalar()
177 .filter(cls.app_settings_name == ldap_key).scalar()
178
178
179 @classmethod
179 @classmethod
180 def get_app_settings(cls, cache=False):
180 def get_app_settings(cls, cache=False):
181
181
182 ret = cls.query()
182 ret = cls.query()
183
183
184 if cache:
184 if cache:
185 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
185 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
186
186
187 if not ret:
187 if not ret:
188 raise Exception('Could not get application settings !')
188 raise Exception('Could not get application settings !')
189 settings = {}
189 settings = {}
190 for each in ret:
190 for each in ret:
191 settings['rhodecode_' + each.app_settings_name] = \
191 settings['rhodecode_' + each.app_settings_name] = \
192 each.app_settings_value
192 each.app_settings_value
193
193
194 return settings
194 return settings
195
195
196 @classmethod
196 @classmethod
197 def get_ldap_settings(cls, cache=False):
197 def get_ldap_settings(cls, cache=False):
198 ret = cls.query()\
198 ret = cls.query()\
199 .filter(cls.app_settings_name.startswith('ldap_')).all()
199 .filter(cls.app_settings_name.startswith('ldap_')).all()
200 fd = {}
200 fd = {}
201 for row in ret:
201 for row in ret:
202 fd.update({row.app_settings_name:row.app_settings_value})
202 fd.update({row.app_settings_name:row.app_settings_value})
203
203
204 return fd
204 return fd
205
205
206
206
207 class RhodeCodeUi(Base, BaseModel):
207 class RhodeCodeUi(Base, BaseModel):
208 __tablename__ = 'rhodecode_ui'
208 __tablename__ = 'rhodecode_ui'
209 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
209 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
210
210
211 HOOK_REPO_SIZE = 'changegroup.repo_size'
211 HOOK_REPO_SIZE = 'changegroup.repo_size'
212 HOOK_PUSH = 'pretxnchangegroup.push_logger'
212 HOOK_PUSH = 'pretxnchangegroup.push_logger'
213 HOOK_PULL = 'preoutgoing.pull_logger'
213 HOOK_PULL = 'preoutgoing.pull_logger'
214
214
215 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
215 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
216 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
216 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
217 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
217 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
218 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
218 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
219 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
219 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
220
220
221
221
222 @classmethod
222 @classmethod
223 def get_by_key(cls, key):
223 def get_by_key(cls, key):
224 return cls.query().filter(cls.ui_key == key)
224 return cls.query().filter(cls.ui_key == key)
225
225
226
226
227 @classmethod
227 @classmethod
228 def get_builtin_hooks(cls):
228 def get_builtin_hooks(cls):
229 q = cls.query()
229 q = cls.query()
230 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
230 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
231 cls.HOOK_PUSH, cls.HOOK_PULL]))
231 cls.HOOK_PUSH, cls.HOOK_PULL]))
232 return q.all()
232 return q.all()
233
233
234 @classmethod
234 @classmethod
235 def get_custom_hooks(cls):
235 def get_custom_hooks(cls):
236 q = cls.query()
236 q = cls.query()
237 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
237 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 q = q.filter(cls.ui_section == 'hooks')
239 q = q.filter(cls.ui_section == 'hooks')
240 return q.all()
240 return q.all()
241
241
242 @classmethod
242 @classmethod
243 def create_or_update_hook(cls, key, val):
243 def create_or_update_hook(cls, key, val):
244 new_ui = cls.get_by_key(key).scalar() or cls()
244 new_ui = cls.get_by_key(key).scalar() or cls()
245 new_ui.ui_section = 'hooks'
245 new_ui.ui_section = 'hooks'
246 new_ui.ui_active = True
246 new_ui.ui_active = True
247 new_ui.ui_key = key
247 new_ui.ui_key = key
248 new_ui.ui_value = val
248 new_ui.ui_value = val
249
249
250 Session.add(new_ui)
250 Session.add(new_ui)
251 Session.commit()
251 Session.commit()
252
252
253
253
254 class User(Base, BaseModel):
254 class User(Base, BaseModel):
255 __tablename__ = 'users'
255 __tablename__ = 'users'
256 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
256 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
257 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
257 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
258 username = Column("username", String(255), nullable=True, unique=None, default=None)
258 username = Column("username", String(255), nullable=True, unique=None, default=None)
259 password = Column("password", String(255), nullable=True, unique=None, default=None)
259 password = Column("password", String(255), nullable=True, unique=None, default=None)
260 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
260 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
261 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
261 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
262 name = Column("name", String(255), nullable=True, unique=None, default=None)
262 name = Column("name", String(255), nullable=True, unique=None, default=None)
263 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
263 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
264 email = Column("email", String(255), nullable=True, unique=None, default=None)
264 email = Column("email", String(255), nullable=True, unique=None, default=None)
265 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
265 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
266 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
266 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
267 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
267 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
268
268
269 user_log = relationship('UserLog', cascade='all')
269 user_log = relationship('UserLog', cascade='all')
270 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
270 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
271
271
272 repositories = relationship('Repository')
272 repositories = relationship('Repository')
273 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
273 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
274 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
274 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
275
275
276 group_member = relationship('UserGroupMember', cascade='all')
276 group_member = relationship('UserGroupMember', cascade='all')
277
277
278 @property
278 @property
279 def full_contact(self):
279 def full_contact(self):
280 return '%s %s <%s>' % (self.name, self.lastname, self.email)
280 return '%s %s <%s>' % (self.name, self.lastname, self.email)
281
281
282 @property
282 @property
283 def short_contact(self):
283 def short_contact(self):
284 return '%s %s' % (self.name, self.lastname)
284 return '%s %s' % (self.name, self.lastname)
285
285
286 @property
286 @property
287 def is_admin(self):
287 def is_admin(self):
288 return self.admin
288 return self.admin
289
289
290 def __repr__(self):
290 def __repr__(self):
291 try:
291 try:
292 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
292 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
293 self.user_id, self.username)
293 self.user_id, self.username)
294 except:
294 except:
295 return self.__class__.__name__
295 return self.__class__.__name__
296
296
297 @classmethod
297 @classmethod
298 def get_by_username(cls, username, case_insensitive=False):
298 def get_by_username(cls, username, case_insensitive=False):
299 if case_insensitive:
299 if case_insensitive:
300 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
300 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
301 else:
301 else:
302 return Session.query(cls).filter(cls.username == username).scalar()
302 return Session.query(cls).filter(cls.username == username).scalar()
303
303
304 @classmethod
304 @classmethod
305 def get_by_auth_token(cls, auth_token):
305 def get_by_auth_token(cls, auth_token):
306 return cls.query().filter(cls.api_key == auth_token).one()
306 return cls.query().filter(cls.api_key == auth_token).one()
307
307
308 def update_lastlogin(self):
308 def update_lastlogin(self):
309 """Update user lastlogin"""
309 """Update user lastlogin"""
310
310
311 self.last_login = datetime.datetime.now()
311 self.last_login = datetime.datetime.now()
312 Session.add(self)
312 Session.add(self)
313 Session.commit()
313 Session.commit()
314 log.debug('updated user %s lastlogin' % self.username)
314 log.debug('updated user %s lastlogin' % self.username)
315
315
316 @classmethod
316 @classmethod
317 def create(cls, form_data):
317 def create(cls, form_data):
318 from rhodecode.lib.auth import get_crypt_password
318 from rhodecode.lib.auth import get_crypt_password
319
319
320 try:
320 try:
321 new_user = cls()
321 new_user = cls()
322 for k, v in form_data.items():
322 for k, v in form_data.items():
323 if k == 'password':
323 if k == 'password':
324 v = get_crypt_password(v)
324 v = get_crypt_password(v)
325 setattr(new_user, k, v)
325 setattr(new_user, k, v)
326
326
327 new_user.api_key = generate_auth_token(form_data['username'])
327 new_user.api_key = generate_auth_token(form_data['username'])
328 Session.add(new_user)
328 Session.add(new_user)
329 Session.commit()
329 Session.commit()
330 return new_user
330 return new_user
331 except:
331 except:
332 log.error(traceback.format_exc())
332 log.error(traceback.format_exc())
333 Session.rollback()
333 Session.rollback()
334 raise
334 raise
335
335
336 class UserLog(Base, BaseModel):
336 class UserLog(Base, BaseModel):
337 __tablename__ = 'user_logs'
337 __tablename__ = 'user_logs'
338 __table_args__ = {'extend_existing':True}
338 __table_args__ = {'extend_existing':True}
339 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
339 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
341 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
341 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
342 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
342 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
343 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
343 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
344 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
344 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
345 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
345 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
346
346
347 @property
347 @property
348 def action_as_day(self):
348 def action_as_day(self):
349 return date(*self.action_date.timetuple()[:3])
349 return date(*self.action_date.timetuple()[:3])
350
350
351 user = relationship('User')
351 user = relationship('User')
352 repository = relationship('Repository')
352 repository = relationship('Repository')
353
353
354
354
355 class UserGroup(Base, BaseModel):
355 class UserGroup(Base, BaseModel):
356 __tablename__ = 'users_groups'
356 __tablename__ = 'users_groups'
357 __table_args__ = {'extend_existing':True}
357 __table_args__ = {'extend_existing':True}
358
358
359 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
360 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
360 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
361 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
361 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
362
362
363 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
363 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
364
364
365 def __repr__(self):
365 def __repr__(self):
366 return '<userGroup(%s)>' % (self.users_group_name)
366 return '<userGroup(%s)>' % (self.users_group_name)
367
367
368 @classmethod
368 @classmethod
369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
370 if case_insensitive:
370 if case_insensitive:
371 gr = cls.query()\
371 gr = cls.query()\
372 .filter(cls.users_group_name.ilike(group_name))
372 .filter(cls.users_group_name.ilike(group_name))
373 else:
373 else:
374 gr = cls.query()\
374 gr = cls.query()\
375 .filter(cls.users_group_name == group_name)
375 .filter(cls.users_group_name == group_name)
376 if cache:
376 if cache:
377 gr = gr.options(FromCache("sql_cache_short",
377 gr = gr.options(FromCache("sql_cache_short",
378 "get_user_%s" % group_name))
378 "get_user_%s" % group_name))
379 return gr.scalar()
379 return gr.scalar()
380
380
381
382 @classmethod
381 @classmethod
383 def get(cls, users_group_id, cache=False):
382 def get(cls, users_group_id, cache=False):
384 users_group = cls.query()
383 users_group = cls.query()
385 if cache:
384 if cache:
386 users_group = users_group.options(FromCache("sql_cache_short",
385 users_group = users_group.options(FromCache("sql_cache_short",
387 "get_users_group_%s" % users_group_id))
386 "get_users_group_%s" % users_group_id))
388 return users_group.get(users_group_id)
387 return users_group.get(users_group_id)
389
388
390 @classmethod
389 @classmethod
391 def create(cls, form_data):
390 def create(cls, form_data):
392 try:
391 try:
393 new_users_group = cls()
392 new_user_group = cls()
394 for k, v in form_data.items():
393 for k, v in form_data.items():
395 setattr(new_users_group, k, v)
394 setattr(new_user_group, k, v)
396
395
397 Session.add(new_users_group)
396 Session.add(new_user_group)
398 Session.commit()
397 Session.commit()
399 return new_users_group
398 return new_user_group
400 except:
399 except:
401 log.error(traceback.format_exc())
400 log.error(traceback.format_exc())
402 Session.rollback()
401 Session.rollback()
403 raise
402 raise
404
403
405 @classmethod
404 @classmethod
406 def update(cls, users_group_id, form_data):
405 def update(cls, users_group_id, form_data):
407
406
408 try:
407 try:
409 users_group = cls.get(users_group_id, cache=False)
408 users_group = cls.get(users_group_id, cache=False)
410
409
411 for k, v in form_data.items():
410 for k, v in form_data.items():
412 if k == 'users_group_members':
411 if k == 'users_group_members':
413 users_group.members = []
412 users_group.members = []
414 Session.flush()
413 Session.flush()
415 members_list = []
414 members_list = []
416 if v:
415 if v:
417 v = [v] if isinstance(v, basestring) else v
416 v = [v] if isinstance(v, basestring) else v
418 for u_id in set(v):
417 for u_id in set(v):
419 member = UserGroupMember(users_group_id, u_id)
418 member = UserGroupMember(users_group_id, u_id)
420 members_list.append(member)
419 members_list.append(member)
421 setattr(users_group, 'members', members_list)
420 setattr(users_group, 'members', members_list)
422 setattr(users_group, k, v)
421 setattr(users_group, k, v)
423
422
424 Session.add(users_group)
423 Session.add(users_group)
425 Session.commit()
424 Session.commit()
426 except:
425 except:
427 log.error(traceback.format_exc())
426 log.error(traceback.format_exc())
428 Session.rollback()
427 Session.rollback()
429 raise
428 raise
430
429
431 @classmethod
430 @classmethod
432 def delete(cls, user_group_id):
431 def delete(cls, user_group_id):
433 try:
432 try:
434
433
435 # check if this group is not assigned to repo
434 # check if this group is not assigned to repo
436 assigned_groups = UserGroupRepoToPerm.query()\
435 assigned_groups = UserGroupRepoToPerm.query()\
437 .filter(UserGroupRepoToPerm.users_group_id ==
436 .filter(UserGroupRepoToPerm.users_group_id ==
438 user_group_id).all()
437 user_group_id).all()
439
438
440 if assigned_groups:
439 if assigned_groups:
441 raise UserGroupAssignedException(
440 raise UserGroupAssignedException(
442 'UserGroup assigned to %s' % assigned_groups)
441 'UserGroup assigned to %s' % assigned_groups)
443
442
444 users_group = cls.get(user_group_id, cache=False)
443 users_group = cls.get(user_group_id, cache=False)
445 Session.delete(users_group)
444 Session.delete(users_group)
446 Session.commit()
445 Session.commit()
447 except:
446 except:
448 log.error(traceback.format_exc())
447 log.error(traceback.format_exc())
449 Session.rollback()
448 Session.rollback()
450 raise
449 raise
451
450
452 class UserGroupMember(Base, BaseModel):
451 class UserGroupMember(Base, BaseModel):
453 __tablename__ = 'users_groups_members'
452 __tablename__ = 'users_groups_members'
454 __table_args__ = {'extend_existing':True}
453 __table_args__ = {'extend_existing':True}
455
454
456 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
455 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
457 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
456 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
458 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
459
458
460 user = relationship('User', lazy='joined')
459 user = relationship('User', lazy='joined')
461 users_group = relationship('UserGroup')
460 users_group = relationship('UserGroup')
462
461
463 def __init__(self, gr_id='', u_id=''):
462 def __init__(self, gr_id='', u_id=''):
464 self.users_group_id = gr_id
463 self.users_group_id = gr_id
465 self.user_id = u_id
464 self.user_id = u_id
466
465
467 @staticmethod
466 @staticmethod
468 def add_user_to_group(group, user):
467 def add_user_to_group(group, user):
469 ugm = UserGroupMember()
468 ugm = UserGroupMember()
470 ugm.users_group = group
469 ugm.users_group = group
471 ugm.user = user
470 ugm.user = user
472 Session.add(ugm)
471 Session.add(ugm)
473 Session.commit()
472 Session.commit()
474 return ugm
473 return ugm
475
474
476 class Repository(Base, BaseModel):
475 class Repository(Base, BaseModel):
477 __tablename__ = 'repositories'
476 __tablename__ = 'repositories'
478 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
477 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
479
478
480 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
479 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
480 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
482 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
481 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
483 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
482 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
485 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
484 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
486 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
485 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
487 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
486 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
488 description = Column("description", String(10000), nullable=True, unique=None, default=None)
487 description = Column("description", String(10000), nullable=True, unique=None, default=None)
489 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
490
489
491 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
490 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
492 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
491 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
493
492
494
493
495 user = relationship('User')
494 user = relationship('User')
496 fork = relationship('Repository', remote_side=repo_id)
495 fork = relationship('Repository', remote_side=repo_id)
497 group = relationship('RepoGroup')
496 group = relationship('RepoGroup')
498 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
497 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
499 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
498 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
500 stats = relationship('Statistics', cascade='all', uselist=False)
499 stats = relationship('Statistics', cascade='all', uselist=False)
501
500
502 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
501 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
503
502
504 logs = relationship('UserLog', cascade='all')
503 logs = relationship('UserLog', cascade='all')
505
504
506 def __repr__(self):
505 def __repr__(self):
507 return "<%s('%s:%s')>" % (self.__class__.__name__,
506 return "<%s('%s:%s')>" % (self.__class__.__name__,
508 self.repo_id, self.repo_name)
507 self.repo_id, self.repo_name)
509
508
510 @classmethod
509 @classmethod
511 def url_sep(cls):
510 def url_sep(cls):
512 return '/'
511 return '/'
513
512
514 @classmethod
513 @classmethod
515 def get_by_repo_name(cls, repo_name):
514 def get_by_repo_name(cls, repo_name):
516 q = Session.query(cls).filter(cls.repo_name == repo_name)
515 q = Session.query(cls).filter(cls.repo_name == repo_name)
517 q = q.options(joinedload(Repository.fork))\
516 q = q.options(joinedload(Repository.fork))\
518 .options(joinedload(Repository.user))\
517 .options(joinedload(Repository.user))\
519 .options(joinedload(Repository.group))
518 .options(joinedload(Repository.group))
520 return q.one()
519 return q.one()
521
520
522 @classmethod
521 @classmethod
523 def get_repo_forks(cls, repo_id):
522 def get_repo_forks(cls, repo_id):
524 return cls.query().filter(Repository.fork_id == repo_id)
523 return cls.query().filter(Repository.fork_id == repo_id)
525
524
526 @classmethod
525 @classmethod
527 def base_path(cls):
526 def base_path(cls):
528 """
527 """
529 Returns base path when all repos are stored
528 Returns base path when all repos are stored
530
529
531 :param cls:
530 :param cls:
532 """
531 """
533 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
532 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
534 cls.url_sep())
533 cls.url_sep())
535 q.options(FromCache("sql_cache_short", "repository_repo_path"))
534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
536 return q.one().ui_value
535 return q.one().ui_value
537
536
538 @property
537 @property
539 def just_name(self):
538 def just_name(self):
540 return self.repo_name.split(Repository.url_sep())[-1]
539 return self.repo_name.split(Repository.url_sep())[-1]
541
540
542 @property
541 @property
543 def groups_with_parents(self):
542 def groups_with_parents(self):
544 groups = []
543 groups = []
545 if self.group is None:
544 if self.group is None:
546 return groups
545 return groups
547
546
548 cur_gr = self.group
547 cur_gr = self.group
549 groups.insert(0, cur_gr)
548 groups.insert(0, cur_gr)
550 while 1:
549 while 1:
551 gr = getattr(cur_gr, 'parent_group', None)
550 gr = getattr(cur_gr, 'parent_group', None)
552 cur_gr = cur_gr.parent_group
551 cur_gr = cur_gr.parent_group
553 if gr is None:
552 if gr is None:
554 break
553 break
555 groups.insert(0, gr)
554 groups.insert(0, gr)
556
555
557 return groups
556 return groups
558
557
559 @property
558 @property
560 def groups_and_repo(self):
559 def groups_and_repo(self):
561 return self.groups_with_parents, self.just_name
560 return self.groups_with_parents, self.just_name
562
561
563 @LazyProperty
562 @LazyProperty
564 def repo_path(self):
563 def repo_path(self):
565 """
564 """
566 Returns base full path for that repository means where it actually
565 Returns base full path for that repository means where it actually
567 exists on a filesystem
566 exists on a filesystem
568 """
567 """
569 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
568 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
570 Repository.url_sep())
569 Repository.url_sep())
571 q.options(FromCache("sql_cache_short", "repository_repo_path"))
570 q.options(FromCache("sql_cache_short", "repository_repo_path"))
572 return q.one().ui_value
571 return q.one().ui_value
573
572
574 @property
573 @property
575 def repo_full_path(self):
574 def repo_full_path(self):
576 p = [self.repo_path]
575 p = [self.repo_path]
577 # we need to split the name by / since this is how we store the
576 # we need to split the name by / since this is how we store the
578 # names in the database, but that eventually needs to be converted
577 # names in the database, but that eventually needs to be converted
579 # into a valid system path
578 # into a valid system path
580 p += self.repo_name.split(Repository.url_sep())
579 p += self.repo_name.split(Repository.url_sep())
581 return os.path.join(*p)
580 return os.path.join(*p)
582
581
583 def get_new_name(self, repo_name):
582 def get_new_name(self, repo_name):
584 """
583 """
585 returns new full repository name based on assigned group and new new
584 returns new full repository name based on assigned group and new new
586
585
587 :param group_name:
586 :param group_name:
588 """
587 """
589 path_prefix = self.group.full_path_splitted if self.group else []
588 path_prefix = self.group.full_path_splitted if self.group else []
590 return Repository.url_sep().join(path_prefix + [repo_name])
589 return Repository.url_sep().join(path_prefix + [repo_name])
591
590
592 @property
591 @property
593 def _config(self):
592 def _config(self):
594 """
593 """
595 Returns db based config object.
594 Returns db based config object.
596 """
595 """
597 from rhodecode.lib.utils import make_db_config
596 from rhodecode.lib.utils import make_db_config
598 return make_db_config(clear_session=False)
597 return make_db_config(clear_session=False)
599
598
600 @classmethod
599 @classmethod
601 def is_valid(cls, repo_name):
600 def is_valid(cls, repo_name):
602 """
601 """
603 returns True if given repo name is a valid filesystem repository
602 returns True if given repo name is a valid filesystem repository
604
603
605 :param cls:
604 :param cls:
606 :param repo_name:
605 :param repo_name:
607 """
606 """
608 from rhodecode.lib.utils import is_valid_repo
607 from rhodecode.lib.utils import is_valid_repo
609
608
610 return is_valid_repo(repo_name, cls.base_path())
609 return is_valid_repo(repo_name, cls.base_path())
611
610
612
611
613 #==========================================================================
612 #==========================================================================
614 # SCM PROPERTIES
613 # SCM PROPERTIES
615 #==========================================================================
614 #==========================================================================
616
615
617 def get_commit(self, rev):
616 def get_commit(self, rev):
618 return get_commit_safe(self.scm_instance, rev)
617 return get_commit_safe(self.scm_instance, rev)
619
618
620 @property
619 @property
621 def tip(self):
620 def tip(self):
622 return self.get_commit('tip')
621 return self.get_commit('tip')
623
622
624 @property
623 @property
625 def author(self):
624 def author(self):
626 return self.tip.author
625 return self.tip.author
627
626
628 @property
627 @property
629 def last_change(self):
628 def last_change(self):
630 return self.scm_instance.last_change
629 return self.scm_instance.last_change
631
630
632 #==========================================================================
631 #==========================================================================
633 # SCM CACHE INSTANCE
632 # SCM CACHE INSTANCE
634 #==========================================================================
633 #==========================================================================
635
634
636 @property
635 @property
637 def invalidate(self):
636 def invalidate(self):
638 return CacheInvalidation.invalidate(self.repo_name)
637 return CacheInvalidation.invalidate(self.repo_name)
639
638
640 def set_invalidate(self):
639 def set_invalidate(self):
641 """
640 """
642 set a cache for invalidation for this instance
641 set a cache for invalidation for this instance
643 """
642 """
644 CacheInvalidation.set_invalidate(self.repo_name)
643 CacheInvalidation.set_invalidate(self.repo_name)
645
644
646 @LazyProperty
645 @LazyProperty
647 def scm_instance(self):
646 def scm_instance(self):
648 return self.__get_instance()
647 return self.__get_instance()
649
648
650 @property
649 @property
651 def scm_instance_cached(self):
650 def scm_instance_cached(self):
652 @cache_region('long_term')
651 @cache_region('long_term')
653 def _c(repo_name):
652 def _c(repo_name):
654 return self.__get_instance()
653 return self.__get_instance()
655 rn = self.repo_name
654 rn = self.repo_name
656
655
657 inv = self.invalidate
656 inv = self.invalidate
658 if inv is not None:
657 if inv is not None:
659 region_invalidate(_c, None, rn)
658 region_invalidate(_c, None, rn)
660 # update our cache
659 # update our cache
661 CacheInvalidation.set_valid(inv.cache_key)
660 CacheInvalidation.set_valid(inv.cache_key)
662 return _c(rn)
661 return _c(rn)
663
662
664 def __get_instance(self):
663 def __get_instance(self):
665
664
666 repo_full_path = self.repo_full_path
665 repo_full_path = self.repo_full_path
667
666
668 try:
667 try:
669 alias = get_scm(repo_full_path)[0]
668 alias = get_scm(repo_full_path)[0]
670 log.debug('Creating instance of %s repository' % alias)
669 log.debug('Creating instance of %s repository' % alias)
671 backend = get_backend(alias)
670 backend = get_backend(alias)
672 except VCSError:
671 except VCSError:
673 log.error(traceback.format_exc())
672 log.error(traceback.format_exc())
674 log.error('Perhaps this repository is in db and not in '
673 log.error('Perhaps this repository is in db and not in '
675 'filesystem run rescan repositories with '
674 'filesystem run rescan repositories with '
676 '"destroy old data " option from admin panel')
675 '"destroy old data " option from admin panel')
677 return
676 return
678
677
679 if alias == 'hg':
678 if alias == 'hg':
680
679
681 repo = backend(safe_str(repo_full_path), create=False,
680 repo = backend(safe_str(repo_full_path), create=False,
682 config=self._config)
681 config=self._config)
683
682
684 else:
683 else:
685 repo = backend(repo_full_path, create=False)
684 repo = backend(repo_full_path, create=False)
686
685
687 return repo
686 return repo
688
687
689
688
690 class Group(Base, BaseModel):
689 class Group(Base, BaseModel):
691 __tablename__ = 'groups'
690 __tablename__ = 'groups'
692 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
691 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
693 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
692 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
694 __mapper_args__ = {'order_by':'group_name'}
693 __mapper_args__ = {'order_by':'group_name'}
695
694
696 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
695 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
697 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
696 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
698 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
697 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
699 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
698 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
700
699
701 parent_group = relationship('Group', remote_side=group_id)
700 parent_group = relationship('Group', remote_side=group_id)
702
701
703 def __init__(self, group_name='', parent_group=None):
702 def __init__(self, group_name='', parent_group=None):
704 self.group_name = group_name
703 self.group_name = group_name
705 self.parent_group = parent_group
704 self.parent_group = parent_group
706
705
707 def __repr__(self):
706 def __repr__(self):
708 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
707 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
709 self.group_name)
708 self.group_name)
710
709
711 @classmethod
710 @classmethod
712 def url_sep(cls):
711 def url_sep(cls):
713 return '/'
712 return '/'
714
713
715 @classmethod
714 @classmethod
716 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
715 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
717 if case_insensitive:
716 if case_insensitive:
718 gr = cls.query()\
717 gr = cls.query()\
719 .filter(cls.group_name.ilike(group_name))
718 .filter(cls.group_name.ilike(group_name))
720 else:
719 else:
721 gr = cls.query()\
720 gr = cls.query()\
722 .filter(cls.group_name == group_name)
721 .filter(cls.group_name == group_name)
723 if cache:
722 if cache:
724 gr = gr.options(FromCache("sql_cache_short",
723 gr = gr.options(FromCache("sql_cache_short",
725 "get_group_%s" % group_name))
724 "get_group_%s" % group_name))
726 return gr.scalar()
725 return gr.scalar()
727
726
728 @property
727 @property
729 def parents(self):
728 def parents(self):
730 parents_recursion_limit = 5
729 parents_recursion_limit = 5
731 groups = []
730 groups = []
732 if self.parent_group is None:
731 if self.parent_group is None:
733 return groups
732 return groups
734 cur_gr = self.parent_group
733 cur_gr = self.parent_group
735 groups.insert(0, cur_gr)
734 groups.insert(0, cur_gr)
736 cnt = 0
735 cnt = 0
737 while 1:
736 while 1:
738 cnt += 1
737 cnt += 1
739 gr = getattr(cur_gr, 'parent_group', None)
738 gr = getattr(cur_gr, 'parent_group', None)
740 cur_gr = cur_gr.parent_group
739 cur_gr = cur_gr.parent_group
741 if gr is None:
740 if gr is None:
742 break
741 break
743 if cnt == parents_recursion_limit:
742 if cnt == parents_recursion_limit:
744 # this will prevent accidental infinit loops
743 # this will prevent accidental infinit loops
745 log.error('group nested more than %s' %
744 log.error('group nested more than %s' %
746 parents_recursion_limit)
745 parents_recursion_limit)
747 break
746 break
748
747
749 groups.insert(0, gr)
748 groups.insert(0, gr)
750 return groups
749 return groups
751
750
752 @property
751 @property
753 def children(self):
752 def children(self):
754 return Group.query().filter(Group.parent_group == self)
753 return Group.query().filter(Group.parent_group == self)
755
754
756 @property
755 @property
757 def name(self):
756 def name(self):
758 return self.group_name.split(Group.url_sep())[-1]
757 return self.group_name.split(Group.url_sep())[-1]
759
758
760 @property
759 @property
761 def full_path(self):
760 def full_path(self):
762 return self.group_name
761 return self.group_name
763
762
764 @property
763 @property
765 def full_path_splitted(self):
764 def full_path_splitted(self):
766 return self.group_name.split(Group.url_sep())
765 return self.group_name.split(Group.url_sep())
767
766
768 @property
767 @property
769 def repositories(self):
768 def repositories(self):
770 return Repository.query().filter(Repository.group == self)
769 return Repository.query().filter(Repository.group == self)
771
770
772 @property
771 @property
773 def repositories_recursive_count(self):
772 def repositories_recursive_count(self):
774 cnt = self.repositories.count()
773 cnt = self.repositories.count()
775
774
776 def children_count(group):
775 def children_count(group):
777 cnt = 0
776 cnt = 0
778 for child in group.children:
777 for child in group.children:
779 cnt += child.repositories.count()
778 cnt += child.repositories.count()
780 cnt += children_count(child)
779 cnt += children_count(child)
781 return cnt
780 return cnt
782
781
783 return cnt + children_count(self)
782 return cnt + children_count(self)
784
783
785
784
786 def get_new_name(self, group_name):
785 def get_new_name(self, group_name):
787 """
786 """
788 returns new full group name based on parent and new name
787 returns new full group name based on parent and new name
789
788
790 :param group_name:
789 :param group_name:
791 """
790 """
792 path_prefix = (self.parent_group.full_path_splitted if
791 path_prefix = (self.parent_group.full_path_splitted if
793 self.parent_group else [])
792 self.parent_group else [])
794 return Group.url_sep().join(path_prefix + [group_name])
793 return Group.url_sep().join(path_prefix + [group_name])
795
794
796
795
797 class Permission(Base, BaseModel):
796 class Permission(Base, BaseModel):
798 __tablename__ = 'permissions'
797 __tablename__ = 'permissions'
799 __table_args__ = {'extend_existing':True}
798 __table_args__ = {'extend_existing':True}
800 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
799 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
801 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
800 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
802 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
801 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
803
802
804 def __repr__(self):
803 def __repr__(self):
805 return "<%s('%s:%s')>" % (self.__class__.__name__,
804 return "<%s('%s:%s')>" % (self.__class__.__name__,
806 self.permission_id, self.permission_name)
805 self.permission_id, self.permission_name)
807
806
808 @classmethod
807 @classmethod
809 def get_by_key(cls, key):
808 def get_by_key(cls, key):
810 return cls.query().filter(cls.permission_name == key).scalar()
809 return cls.query().filter(cls.permission_name == key).scalar()
811
810
812 class UserRepoToPerm(Base, BaseModel):
811 class UserRepoToPerm(Base, BaseModel):
813 __tablename__ = 'repo_to_perm'
812 __tablename__ = 'repo_to_perm'
814 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
813 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
815 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
814 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
815 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
817 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
819
818
820 user = relationship('User')
819 user = relationship('User')
821 permission = relationship('Permission')
820 permission = relationship('Permission')
822 repository = relationship('Repository')
821 repository = relationship('Repository')
823
822
824 class UserToPerm(Base, BaseModel):
823 class UserToPerm(Base, BaseModel):
825 __tablename__ = 'user_to_perm'
824 __tablename__ = 'user_to_perm'
826 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
825 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
827 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
826 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
828 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
827 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
829 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
828 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
830
829
831 user = relationship('User')
830 user = relationship('User')
832 permission = relationship('Permission')
831 permission = relationship('Permission')
833
832
834 @classmethod
833 @classmethod
835 def has_perm(cls, user_id, perm):
834 def has_perm(cls, user_id, perm):
836 if not isinstance(perm, Permission):
835 if not isinstance(perm, Permission):
837 raise Exception('perm needs to be an instance of Permission class')
836 raise Exception('perm needs to be an instance of Permission class')
838
837
839 return cls.query().filter(cls.user_id == user_id)\
838 return cls.query().filter(cls.user_id == user_id)\
840 .filter(cls.permission == perm).scalar() is not None
839 .filter(cls.permission == perm).scalar() is not None
841
840
842 @classmethod
841 @classmethod
843 def grant_perm(cls, user_id, perm):
842 def grant_perm(cls, user_id, perm):
844 if not isinstance(perm, Permission):
843 if not isinstance(perm, Permission):
845 raise Exception('perm needs to be an instance of Permission class')
844 raise Exception('perm needs to be an instance of Permission class')
846
845
847 new = cls()
846 new = cls()
848 new.user_id = user_id
847 new.user_id = user_id
849 new.permission = perm
848 new.permission = perm
850 try:
849 try:
851 Session.add(new)
850 Session.add(new)
852 Session.commit()
851 Session.commit()
853 except:
852 except:
854 Session.rollback()
853 Session.rollback()
855
854
856
855
857 @classmethod
856 @classmethod
858 def revoke_perm(cls, user_id, perm):
857 def revoke_perm(cls, user_id, perm):
859 if not isinstance(perm, Permission):
858 if not isinstance(perm, Permission):
860 raise Exception('perm needs to be an instance of Permission class')
859 raise Exception('perm needs to be an instance of Permission class')
861
860
862 try:
861 try:
863 cls.query().filter(cls.user_id == user_id) \
862 cls.query().filter(cls.user_id == user_id) \
864 .filter(cls.permission == perm).delete()
863 .filter(cls.permission == perm).delete()
865 Session.commit()
864 Session.commit()
866 except:
865 except:
867 Session.rollback()
866 Session.rollback()
868
867
869 class UserGroupRepoToPerm(Base, BaseModel):
868 class UserGroupRepoToPerm(Base, BaseModel):
870 __tablename__ = 'users_group_repo_to_perm'
869 __tablename__ = 'users_group_repo_to_perm'
871 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
870 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
872 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
871 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
873 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
872 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
874 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
875 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
876
875
877 users_group = relationship('UserGroup')
876 users_group = relationship('UserGroup')
878 permission = relationship('Permission')
877 permission = relationship('Permission')
879 repository = relationship('Repository')
878 repository = relationship('Repository')
880
879
881 def __repr__(self):
880 def __repr__(self):
882 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
881 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
883
882
884 class UserGroupToPerm(Base, BaseModel):
883 class UserGroupToPerm(Base, BaseModel):
885 __tablename__ = 'users_group_to_perm'
884 __tablename__ = 'users_group_to_perm'
886 __table_args__ = {'extend_existing':True}
885 __table_args__ = {'extend_existing':True}
887 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
888 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
889 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
890
889
891 users_group = relationship('UserGroup')
890 users_group = relationship('UserGroup')
892 permission = relationship('Permission')
891 permission = relationship('Permission')
893
892
894
893
895 @classmethod
894 @classmethod
896 def has_perm(cls, users_group_id, perm):
895 def has_perm(cls, users_group_id, perm):
897 if not isinstance(perm, Permission):
896 if not isinstance(perm, Permission):
898 raise Exception('perm needs to be an instance of Permission class')
897 raise Exception('perm needs to be an instance of Permission class')
899
898
900 return cls.query().filter(cls.users_group_id ==
899 return cls.query().filter(cls.users_group_id ==
901 users_group_id)\
900 users_group_id)\
902 .filter(cls.permission == perm)\
901 .filter(cls.permission == perm)\
903 .scalar() is not None
902 .scalar() is not None
904
903
905 @classmethod
904 @classmethod
906 def grant_perm(cls, users_group_id, perm):
905 def grant_perm(cls, users_group_id, perm):
907 if not isinstance(perm, Permission):
906 if not isinstance(perm, Permission):
908 raise Exception('perm needs to be an instance of Permission class')
907 raise Exception('perm needs to be an instance of Permission class')
909
908
910 new = cls()
909 new = cls()
911 new.users_group_id = users_group_id
910 new.users_group_id = users_group_id
912 new.permission = perm
911 new.permission = perm
913 try:
912 try:
914 Session.add(new)
913 Session.add(new)
915 Session.commit()
914 Session.commit()
916 except:
915 except:
917 Session.rollback()
916 Session.rollback()
918
917
919
918
920 @classmethod
919 @classmethod
921 def revoke_perm(cls, users_group_id, perm):
920 def revoke_perm(cls, users_group_id, perm):
922 if not isinstance(perm, Permission):
921 if not isinstance(perm, Permission):
923 raise Exception('perm needs to be an instance of Permission class')
922 raise Exception('perm needs to be an instance of Permission class')
924
923
925 try:
924 try:
926 cls.query().filter(cls.users_group_id == users_group_id) \
925 cls.query().filter(cls.users_group_id == users_group_id) \
927 .filter(cls.permission == perm).delete()
926 .filter(cls.permission == perm).delete()
928 Session.commit()
927 Session.commit()
929 except:
928 except:
930 Session.rollback()
929 Session.rollback()
931
930
932
931
933 class UserRepoGroupToPerm(Base, BaseModel):
932 class UserRepoGroupToPerm(Base, BaseModel):
934 __tablename__ = 'group_to_perm'
933 __tablename__ = 'group_to_perm'
935 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
934 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
936
935
937 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
939 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
939 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
941
940
942 user = relationship('User')
941 user = relationship('User')
943 permission = relationship('Permission')
942 permission = relationship('Permission')
944 group = relationship('RepoGroup')
943 group = relationship('RepoGroup')
945
944
946 class Statistics(Base, BaseModel):
945 class Statistics(Base, BaseModel):
947 __tablename__ = 'statistics'
946 __tablename__ = 'statistics'
948 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
947 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
949 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
949 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
951 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
950 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
952 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
951 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
953 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
952 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
954 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
953 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
955
954
956 repository = relationship('Repository', single_parent=True)
955 repository = relationship('Repository', single_parent=True)
957
956
958 class UserFollowing(Base, BaseModel):
957 class UserFollowing(Base, BaseModel):
959 __tablename__ = 'user_followings'
958 __tablename__ = 'user_followings'
960 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
959 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
961 UniqueConstraint('user_id', 'follows_user_id')
960 UniqueConstraint('user_id', 'follows_user_id')
962 , {'extend_existing':True})
961 , {'extend_existing':True})
963
962
964 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
963 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
965 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
966 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
965 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
967 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
966 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
967 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
969
968
970 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
969 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
971
970
972 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
971 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
973 follows_repository = relationship('Repository', order_by='Repository.repo_name')
972 follows_repository = relationship('Repository', order_by='Repository.repo_name')
974
973
975
974
976 @classmethod
975 @classmethod
977 def get_repo_followers(cls, repo_id):
976 def get_repo_followers(cls, repo_id):
978 return cls.query().filter(cls.follows_repo_id == repo_id)
977 return cls.query().filter(cls.follows_repo_id == repo_id)
979
978
980 class CacheInvalidation(Base, BaseModel):
979 class CacheInvalidation(Base, BaseModel):
981 __tablename__ = 'cache_invalidation'
980 __tablename__ = 'cache_invalidation'
982 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
981 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
983 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
983 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
985 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
984 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
986 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
985 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
987
986
988
987
989 def __init__(self, cache_key, cache_args=''):
988 def __init__(self, cache_key, cache_args=''):
990 self.cache_key = cache_key
989 self.cache_key = cache_key
991 self.cache_args = cache_args
990 self.cache_args = cache_args
992 self.cache_active = False
991 self.cache_active = False
993
992
994 def __repr__(self):
993 def __repr__(self):
995 return "<%s('%s:%s')>" % (self.__class__.__name__,
994 return "<%s('%s:%s')>" % (self.__class__.__name__,
996 self.cache_id, self.cache_key)
995 self.cache_id, self.cache_key)
997
996
998 @classmethod
997 @classmethod
999 def invalidate(cls, key):
998 def invalidate(cls, key):
1000 """
999 """
1001 Returns Invalidation object if this given key should be invalidated
1000 Returns Invalidation object if this given key should be invalidated
1002 None otherwise. `cache_active = False` means that this cache
1001 None otherwise. `cache_active = False` means that this cache
1003 state is not valid and needs to be invalidated
1002 state is not valid and needs to be invalidated
1004
1003
1005 :param key:
1004 :param key:
1006 """
1005 """
1007 return cls.query()\
1006 return cls.query()\
1008 .filter(CacheInvalidation.cache_key == key)\
1007 .filter(CacheInvalidation.cache_key == key)\
1009 .filter(CacheInvalidation.cache_active == False)\
1008 .filter(CacheInvalidation.cache_active == False)\
1010 .scalar()
1009 .scalar()
1011
1010
1012 @classmethod
1011 @classmethod
1013 def set_invalidate(cls, key):
1012 def set_invalidate(cls, key):
1014 """
1013 """
1015 Mark this Cache key for invalidation
1014 Mark this Cache key for invalidation
1016
1015
1017 :param key:
1016 :param key:
1018 """
1017 """
1019
1018
1020 log.debug('marking %s for invalidation' % key)
1019 log.debug('marking %s for invalidation' % key)
1021 inv_obj = Session.query(cls)\
1020 inv_obj = Session.query(cls)\
1022 .filter(cls.cache_key == key).scalar()
1021 .filter(cls.cache_key == key).scalar()
1023 if inv_obj:
1022 if inv_obj:
1024 inv_obj.cache_active = False
1023 inv_obj.cache_active = False
1025 else:
1024 else:
1026 log.debug('cache key not found in invalidation db -> creating one')
1025 log.debug('cache key not found in invalidation db -> creating one')
1027 inv_obj = CacheInvalidation(key)
1026 inv_obj = CacheInvalidation(key)
1028
1027
1029 try:
1028 try:
1030 Session.add(inv_obj)
1029 Session.add(inv_obj)
1031 Session.commit()
1030 Session.commit()
1032 except Exception:
1031 except Exception:
1033 log.error(traceback.format_exc())
1032 log.error(traceback.format_exc())
1034 Session.rollback()
1033 Session.rollback()
1035
1034
1036 @classmethod
1035 @classmethod
1037 def set_valid(cls, key):
1036 def set_valid(cls, key):
1038 """
1037 """
1039 Mark this cache key as active and currently cached
1038 Mark this cache key as active and currently cached
1040
1039
1041 :param key:
1040 :param key:
1042 """
1041 """
1043 inv_obj = Session.query(CacheInvalidation)\
1042 inv_obj = Session.query(CacheInvalidation)\
1044 .filter(CacheInvalidation.cache_key == key).scalar()
1043 .filter(CacheInvalidation.cache_key == key).scalar()
1045 inv_obj.cache_active = True
1044 inv_obj.cache_active = True
1046 Session.add(inv_obj)
1045 Session.add(inv_obj)
1047 Session.commit()
1046 Session.commit()
1048
1047
1049 class DbMigrateVersion(Base, BaseModel):
1048 class DbMigrateVersion(Base, BaseModel):
1050 __tablename__ = 'db_migrate_version'
1049 __tablename__ = 'db_migrate_version'
1051 __table_args__ = {'extend_existing':True}
1050 __table_args__ = {'extend_existing':True}
1052 repository_id = Column('repository_id', String(250), primary_key=True)
1051 repository_id = Column('repository_id', String(250), primary_key=True)
1053 repository_path = Column('repository_path', Text)
1052 repository_path = Column('repository_path', Text)
1054 version = Column('version', Integer)
1053 version = Column('version', Integer)
@@ -1,973 +1,980 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 _repo = ''
99 _repo = ''
100 if isinstance(request, Request):
100 if isinstance(request, Request):
101 if hasattr(request, 'db_repo'):
101 if hasattr(request, 'db_repo'):
102 # if our requests has set db reference use it for name, this
102 # if our requests has set db reference use it for name, this
103 # translates the example.com/_<id> into proper repo names
103 # translates the example.com/_<id> into proper repo names
104 _repo = request.db_repo.repo_name
104 _repo = request.db_repo.repo_name
105 elif getattr(request, 'matchdict', None):
105 elif getattr(request, 'matchdict', None):
106 # pyramid
106 # pyramid
107 _repo = request.matchdict.get('repo_name')
107 _repo = request.matchdict.get('repo_name')
108
108
109 # TODO(marcink): remove after pylons migration...
109 # TODO(marcink): remove after pylons migration...
110 if not _repo:
110 if not _repo:
111 _repo = request.environ['pylons.routes_dict'].get('repo_name')
111 _repo = request.environ['pylons.routes_dict'].get('repo_name')
112
112
113 if _repo:
113 if _repo:
114 _repo = _repo.rstrip('/')
114 _repo = _repo.rstrip('/')
115 return _repo
115 return _repo
116
116
117
117
118 def get_repo_group_slug(request):
118 def get_repo_group_slug(request):
119 _group = ''
119 _group = ''
120 if isinstance(request, Request):
120 if isinstance(request, Request):
121 if hasattr(request, 'db_repo_group'):
121 if hasattr(request, 'db_repo_group'):
122 # if our requests has set db reference use it for name, this
122 # if our requests has set db reference use it for name, this
123 # translates the example.com/_<id> into proper repo group names
123 # translates the example.com/_<id> into proper repo group names
124 _group = request.db_repo_group.group_name
124 _group = request.db_repo_group.group_name
125 elif getattr(request, 'matchdict', None):
125 elif getattr(request, 'matchdict', None):
126 # pyramid
126 # pyramid
127 _group = request.matchdict.get('repo_group_name')
127 _group = request.matchdict.get('repo_group_name')
128
128
129 # TODO(marcink): remove after pylons migration...
129 # TODO(marcink): remove after pylons migration...
130 if not _group:
130 if not _group:
131 _group = request.environ['pylons.routes_dict'].get('group_name')
131 _group = request.environ['pylons.routes_dict'].get('group_name')
132
132
133 if _group:
133 if _group:
134 _group = _group.rstrip('/')
134 _group = _group.rstrip('/')
135 return _group
135 return _group
136
136
137
137
138 def get_user_group_slug(request):
138 def get_user_group_slug(request):
139 if isinstance(request, Request) and getattr(request, 'matchdict', None):
139 _user_group = ''
140 # pyramid
140 if isinstance(request, Request):
141 _group = request.matchdict.get('user_group_id')
141
142 else:
142 if hasattr(request, 'db_user_group'):
143 _group = request.environ['pylons.routes_dict'].get('user_group_id')
143 _user_group = request.db_user_group.users_group_name
144 elif getattr(request, 'matchdict', None):
145 # pyramid
146 _user_group = request.matchdict.get('user_group_id')
144
147
145 try:
148 try:
146 _group = UserGroup.get(_group)
149 _user_group = UserGroup.get(_user_group)
147 if _group:
150 if _user_group:
148 _group = _group.users_group_name
151 _user_group = _user_group.users_group_name
149 except Exception:
152 except Exception:
150 log.exception('Failed to get user group by id')
153 log.exception('Failed to get user group by id')
151 # catch all failures here
154 # catch all failures here
152 return None
155 return None
153
156
154 return _group
157 # TODO(marcink): remove after pylons migration...
158 if not _user_group:
159 _user_group = request.environ['pylons.routes_dict'].get('user_group_id')
160
161 return _user_group
155
162
156
163
157 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
164 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
158 """
165 """
159 Scans given path for repos and return (name,(type,path)) tuple
166 Scans given path for repos and return (name,(type,path)) tuple
160
167
161 :param path: path to scan for repositories
168 :param path: path to scan for repositories
162 :param recursive: recursive search and return names with subdirs in front
169 :param recursive: recursive search and return names with subdirs in front
163 """
170 """
164
171
165 # remove ending slash for better results
172 # remove ending slash for better results
166 path = path.rstrip(os.sep)
173 path = path.rstrip(os.sep)
167 log.debug('now scanning in %s location recursive:%s...', path, recursive)
174 log.debug('now scanning in %s location recursive:%s...', path, recursive)
168
175
169 def _get_repos(p):
176 def _get_repos(p):
170 dirpaths = _get_dirpaths(p)
177 dirpaths = _get_dirpaths(p)
171 if not _is_dir_writable(p):
178 if not _is_dir_writable(p):
172 log.warning('repo path without write access: %s', p)
179 log.warning('repo path without write access: %s', p)
173
180
174 for dirpath in dirpaths:
181 for dirpath in dirpaths:
175 if os.path.isfile(os.path.join(p, dirpath)):
182 if os.path.isfile(os.path.join(p, dirpath)):
176 continue
183 continue
177 cur_path = os.path.join(p, dirpath)
184 cur_path = os.path.join(p, dirpath)
178
185
179 # skip removed repos
186 # skip removed repos
180 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
187 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
181 continue
188 continue
182
189
183 #skip .<somethin> dirs
190 #skip .<somethin> dirs
184 if dirpath.startswith('.'):
191 if dirpath.startswith('.'):
185 continue
192 continue
186
193
187 try:
194 try:
188 scm_info = get_scm(cur_path)
195 scm_info = get_scm(cur_path)
189 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
196 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
190 except VCSError:
197 except VCSError:
191 if not recursive:
198 if not recursive:
192 continue
199 continue
193 #check if this dir containts other repos for recursive scan
200 #check if this dir containts other repos for recursive scan
194 rec_path = os.path.join(p, dirpath)
201 rec_path = os.path.join(p, dirpath)
195 if os.path.isdir(rec_path):
202 if os.path.isdir(rec_path):
196 for inner_scm in _get_repos(rec_path):
203 for inner_scm in _get_repos(rec_path):
197 yield inner_scm
204 yield inner_scm
198
205
199 return _get_repos(path)
206 return _get_repos(path)
200
207
201
208
202 def _get_dirpaths(p):
209 def _get_dirpaths(p):
203 try:
210 try:
204 # OS-independable way of checking if we have at least read-only
211 # OS-independable way of checking if we have at least read-only
205 # access or not.
212 # access or not.
206 dirpaths = os.listdir(p)
213 dirpaths = os.listdir(p)
207 except OSError:
214 except OSError:
208 log.warning('ignoring repo path without read access: %s', p)
215 log.warning('ignoring repo path without read access: %s', p)
209 return []
216 return []
210
217
211 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
218 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
212 # decode paths and suddenly returns unicode objects itself. The items it
219 # decode paths and suddenly returns unicode objects itself. The items it
213 # cannot decode are returned as strings and cause issues.
220 # cannot decode are returned as strings and cause issues.
214 #
221 #
215 # Those paths are ignored here until a solid solution for path handling has
222 # Those paths are ignored here until a solid solution for path handling has
216 # been built.
223 # been built.
217 expected_type = type(p)
224 expected_type = type(p)
218
225
219 def _has_correct_type(item):
226 def _has_correct_type(item):
220 if type(item) is not expected_type:
227 if type(item) is not expected_type:
221 log.error(
228 log.error(
222 u"Ignoring path %s since it cannot be decoded into unicode.",
229 u"Ignoring path %s since it cannot be decoded into unicode.",
223 # Using "repr" to make sure that we see the byte value in case
230 # Using "repr" to make sure that we see the byte value in case
224 # of support.
231 # of support.
225 repr(item))
232 repr(item))
226 return False
233 return False
227 return True
234 return True
228
235
229 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
236 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
230
237
231 return dirpaths
238 return dirpaths
232
239
233
240
234 def _is_dir_writable(path):
241 def _is_dir_writable(path):
235 """
242 """
236 Probe if `path` is writable.
243 Probe if `path` is writable.
237
244
238 Due to trouble on Cygwin / Windows, this is actually probing if it is
245 Due to trouble on Cygwin / Windows, this is actually probing if it is
239 possible to create a file inside of `path`, stat does not produce reliable
246 possible to create a file inside of `path`, stat does not produce reliable
240 results in this case.
247 results in this case.
241 """
248 """
242 try:
249 try:
243 with tempfile.TemporaryFile(dir=path):
250 with tempfile.TemporaryFile(dir=path):
244 pass
251 pass
245 except OSError:
252 except OSError:
246 return False
253 return False
247 return True
254 return True
248
255
249
256
250 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
257 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
251 """
258 """
252 Returns True if given path is a valid repository False otherwise.
259 Returns True if given path is a valid repository False otherwise.
253 If expect_scm param is given also, compare if given scm is the same
260 If expect_scm param is given also, compare if given scm is the same
254 as expected from scm parameter. If explicit_scm is given don't try to
261 as expected from scm parameter. If explicit_scm is given don't try to
255 detect the scm, just use the given one to check if repo is valid
262 detect the scm, just use the given one to check if repo is valid
256
263
257 :param repo_name:
264 :param repo_name:
258 :param base_path:
265 :param base_path:
259 :param expect_scm:
266 :param expect_scm:
260 :param explicit_scm:
267 :param explicit_scm:
261
268
262 :return True: if given path is a valid repository
269 :return True: if given path is a valid repository
263 """
270 """
264 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
271 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
265 log.debug('Checking if `%s` is a valid path for repository. '
272 log.debug('Checking if `%s` is a valid path for repository. '
266 'Explicit type: %s', repo_name, explicit_scm)
273 'Explicit type: %s', repo_name, explicit_scm)
267
274
268 try:
275 try:
269 if explicit_scm:
276 if explicit_scm:
270 detected_scms = [get_scm_backend(explicit_scm)]
277 detected_scms = [get_scm_backend(explicit_scm)]
271 else:
278 else:
272 detected_scms = get_scm(full_path)
279 detected_scms = get_scm(full_path)
273
280
274 if expect_scm:
281 if expect_scm:
275 return detected_scms[0] == expect_scm
282 return detected_scms[0] == expect_scm
276 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
283 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
277 return True
284 return True
278 except VCSError:
285 except VCSError:
279 log.debug('path: %s is not a valid repo !', full_path)
286 log.debug('path: %s is not a valid repo !', full_path)
280 return False
287 return False
281
288
282
289
283 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
290 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
284 """
291 """
285 Returns True if given path is a repository group, False otherwise
292 Returns True if given path is a repository group, False otherwise
286
293
287 :param repo_name:
294 :param repo_name:
288 :param base_path:
295 :param base_path:
289 """
296 """
290 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
297 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
291 log.debug('Checking if `%s` is a valid path for repository group',
298 log.debug('Checking if `%s` is a valid path for repository group',
292 repo_group_name)
299 repo_group_name)
293
300
294 # check if it's not a repo
301 # check if it's not a repo
295 if is_valid_repo(repo_group_name, base_path):
302 if is_valid_repo(repo_group_name, base_path):
296 log.debug('Repo called %s exist, it is not a valid '
303 log.debug('Repo called %s exist, it is not a valid '
297 'repo group' % repo_group_name)
304 'repo group' % repo_group_name)
298 return False
305 return False
299
306
300 try:
307 try:
301 # we need to check bare git repos at higher level
308 # we need to check bare git repos at higher level
302 # since we might match branches/hooks/info/objects or possible
309 # since we might match branches/hooks/info/objects or possible
303 # other things inside bare git repo
310 # other things inside bare git repo
304 scm_ = get_scm(os.path.dirname(full_path))
311 scm_ = get_scm(os.path.dirname(full_path))
305 log.debug('path: %s is a vcs object:%s, not valid '
312 log.debug('path: %s is a vcs object:%s, not valid '
306 'repo group' % (full_path, scm_))
313 'repo group' % (full_path, scm_))
307 return False
314 return False
308 except VCSError:
315 except VCSError:
309 pass
316 pass
310
317
311 # check if it's a valid path
318 # check if it's a valid path
312 if skip_path_check or os.path.isdir(full_path):
319 if skip_path_check or os.path.isdir(full_path):
313 log.debug('path: %s is a valid repo group !', full_path)
320 log.debug('path: %s is a valid repo group !', full_path)
314 return True
321 return True
315
322
316 log.debug('path: %s is not a valid repo group !', full_path)
323 log.debug('path: %s is not a valid repo group !', full_path)
317 return False
324 return False
318
325
319
326
320 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
327 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
321 while True:
328 while True:
322 ok = raw_input(prompt)
329 ok = raw_input(prompt)
323 if ok.lower() in ('y', 'ye', 'yes'):
330 if ok.lower() in ('y', 'ye', 'yes'):
324 return True
331 return True
325 if ok.lower() in ('n', 'no', 'nop', 'nope'):
332 if ok.lower() in ('n', 'no', 'nop', 'nope'):
326 return False
333 return False
327 retries = retries - 1
334 retries = retries - 1
328 if retries < 0:
335 if retries < 0:
329 raise IOError
336 raise IOError
330 print(complaint)
337 print(complaint)
331
338
332 # propagated from mercurial documentation
339 # propagated from mercurial documentation
333 ui_sections = [
340 ui_sections = [
334 'alias', 'auth',
341 'alias', 'auth',
335 'decode/encode', 'defaults',
342 'decode/encode', 'defaults',
336 'diff', 'email',
343 'diff', 'email',
337 'extensions', 'format',
344 'extensions', 'format',
338 'merge-patterns', 'merge-tools',
345 'merge-patterns', 'merge-tools',
339 'hooks', 'http_proxy',
346 'hooks', 'http_proxy',
340 'smtp', 'patch',
347 'smtp', 'patch',
341 'paths', 'profiling',
348 'paths', 'profiling',
342 'server', 'trusted',
349 'server', 'trusted',
343 'ui', 'web', ]
350 'ui', 'web', ]
344
351
345
352
346 def config_data_from_db(clear_session=True, repo=None):
353 def config_data_from_db(clear_session=True, repo=None):
347 """
354 """
348 Read the configuration data from the database and return configuration
355 Read the configuration data from the database and return configuration
349 tuples.
356 tuples.
350 """
357 """
351 from rhodecode.model.settings import VcsSettingsModel
358 from rhodecode.model.settings import VcsSettingsModel
352
359
353 config = []
360 config = []
354
361
355 sa = meta.Session()
362 sa = meta.Session()
356 settings_model = VcsSettingsModel(repo=repo, sa=sa)
363 settings_model = VcsSettingsModel(repo=repo, sa=sa)
357
364
358 ui_settings = settings_model.get_ui_settings()
365 ui_settings = settings_model.get_ui_settings()
359
366
360 for setting in ui_settings:
367 for setting in ui_settings:
361 if setting.active:
368 if setting.active:
362 log.debug(
369 log.debug(
363 'settings ui from db: [%s] %s=%s',
370 'settings ui from db: [%s] %s=%s',
364 setting.section, setting.key, setting.value)
371 setting.section, setting.key, setting.value)
365 config.append((
372 config.append((
366 safe_str(setting.section), safe_str(setting.key),
373 safe_str(setting.section), safe_str(setting.key),
367 safe_str(setting.value)))
374 safe_str(setting.value)))
368 if setting.key == 'push_ssl':
375 if setting.key == 'push_ssl':
369 # force set push_ssl requirement to False, rhodecode
376 # force set push_ssl requirement to False, rhodecode
370 # handles that
377 # handles that
371 config.append((
378 config.append((
372 safe_str(setting.section), safe_str(setting.key), False))
379 safe_str(setting.section), safe_str(setting.key), False))
373 if clear_session:
380 if clear_session:
374 meta.Session.remove()
381 meta.Session.remove()
375
382
376 # TODO: mikhail: probably it makes no sense to re-read hooks information.
383 # TODO: mikhail: probably it makes no sense to re-read hooks information.
377 # It's already there and activated/deactivated
384 # It's already there and activated/deactivated
378 skip_entries = []
385 skip_entries = []
379 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
386 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
380 if 'pull' not in enabled_hook_classes:
387 if 'pull' not in enabled_hook_classes:
381 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
382 if 'push' not in enabled_hook_classes:
389 if 'push' not in enabled_hook_classes:
383 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
391 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
392 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
386
393
387 config = [entry for entry in config if entry[:2] not in skip_entries]
394 config = [entry for entry in config if entry[:2] not in skip_entries]
388
395
389 return config
396 return config
390
397
391
398
392 def make_db_config(clear_session=True, repo=None):
399 def make_db_config(clear_session=True, repo=None):
393 """
400 """
394 Create a :class:`Config` instance based on the values in the database.
401 Create a :class:`Config` instance based on the values in the database.
395 """
402 """
396 config = Config()
403 config = Config()
397 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
404 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
398 for section, option, value in config_data:
405 for section, option, value in config_data:
399 config.set(section, option, value)
406 config.set(section, option, value)
400 return config
407 return config
401
408
402
409
403 def get_enabled_hook_classes(ui_settings):
410 def get_enabled_hook_classes(ui_settings):
404 """
411 """
405 Return the enabled hook classes.
412 Return the enabled hook classes.
406
413
407 :param ui_settings: List of ui_settings as returned
414 :param ui_settings: List of ui_settings as returned
408 by :meth:`VcsSettingsModel.get_ui_settings`
415 by :meth:`VcsSettingsModel.get_ui_settings`
409
416
410 :return: a list with the enabled hook classes. The order is not guaranteed.
417 :return: a list with the enabled hook classes. The order is not guaranteed.
411 :rtype: list
418 :rtype: list
412 """
419 """
413 enabled_hooks = []
420 enabled_hooks = []
414 active_hook_keys = [
421 active_hook_keys = [
415 key for section, key, value, active in ui_settings
422 key for section, key, value, active in ui_settings
416 if section == 'hooks' and active]
423 if section == 'hooks' and active]
417
424
418 hook_names = {
425 hook_names = {
419 RhodeCodeUi.HOOK_PUSH: 'push',
426 RhodeCodeUi.HOOK_PUSH: 'push',
420 RhodeCodeUi.HOOK_PULL: 'pull',
427 RhodeCodeUi.HOOK_PULL: 'pull',
421 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
428 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
422 }
429 }
423
430
424 for key in active_hook_keys:
431 for key in active_hook_keys:
425 hook = hook_names.get(key)
432 hook = hook_names.get(key)
426 if hook:
433 if hook:
427 enabled_hooks.append(hook)
434 enabled_hooks.append(hook)
428
435
429 return enabled_hooks
436 return enabled_hooks
430
437
431
438
432 def set_rhodecode_config(config):
439 def set_rhodecode_config(config):
433 """
440 """
434 Updates pylons config with new settings from database
441 Updates pylons config with new settings from database
435
442
436 :param config:
443 :param config:
437 """
444 """
438 from rhodecode.model.settings import SettingsModel
445 from rhodecode.model.settings import SettingsModel
439 app_settings = SettingsModel().get_all_settings()
446 app_settings = SettingsModel().get_all_settings()
440
447
441 for k, v in app_settings.items():
448 for k, v in app_settings.items():
442 config[k] = v
449 config[k] = v
443
450
444
451
445 def get_rhodecode_realm():
452 def get_rhodecode_realm():
446 """
453 """
447 Return the rhodecode realm from database.
454 Return the rhodecode realm from database.
448 """
455 """
449 from rhodecode.model.settings import SettingsModel
456 from rhodecode.model.settings import SettingsModel
450 realm = SettingsModel().get_setting_by_name('realm')
457 realm = SettingsModel().get_setting_by_name('realm')
451 return safe_str(realm.app_settings_value)
458 return safe_str(realm.app_settings_value)
452
459
453
460
454 def get_rhodecode_base_path():
461 def get_rhodecode_base_path():
455 """
462 """
456 Returns the base path. The base path is the filesystem path which points
463 Returns the base path. The base path is the filesystem path which points
457 to the repository store.
464 to the repository store.
458 """
465 """
459 from rhodecode.model.settings import SettingsModel
466 from rhodecode.model.settings import SettingsModel
460 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
467 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
461 return safe_str(paths_ui.ui_value)
468 return safe_str(paths_ui.ui_value)
462
469
463
470
464 def map_groups(path):
471 def map_groups(path):
465 """
472 """
466 Given a full path to a repository, create all nested groups that this
473 Given a full path to a repository, create all nested groups that this
467 repo is inside. This function creates parent-child relationships between
474 repo is inside. This function creates parent-child relationships between
468 groups and creates default perms for all new groups.
475 groups and creates default perms for all new groups.
469
476
470 :param paths: full path to repository
477 :param paths: full path to repository
471 """
478 """
472 from rhodecode.model.repo_group import RepoGroupModel
479 from rhodecode.model.repo_group import RepoGroupModel
473 sa = meta.Session()
480 sa = meta.Session()
474 groups = path.split(Repository.NAME_SEP)
481 groups = path.split(Repository.NAME_SEP)
475 parent = None
482 parent = None
476 group = None
483 group = None
477
484
478 # last element is repo in nested groups structure
485 # last element is repo in nested groups structure
479 groups = groups[:-1]
486 groups = groups[:-1]
480 rgm = RepoGroupModel(sa)
487 rgm = RepoGroupModel(sa)
481 owner = User.get_first_super_admin()
488 owner = User.get_first_super_admin()
482 for lvl, group_name in enumerate(groups):
489 for lvl, group_name in enumerate(groups):
483 group_name = '/'.join(groups[:lvl] + [group_name])
490 group_name = '/'.join(groups[:lvl] + [group_name])
484 group = RepoGroup.get_by_group_name(group_name)
491 group = RepoGroup.get_by_group_name(group_name)
485 desc = '%s group' % group_name
492 desc = '%s group' % group_name
486
493
487 # skip folders that are now removed repos
494 # skip folders that are now removed repos
488 if REMOVED_REPO_PAT.match(group_name):
495 if REMOVED_REPO_PAT.match(group_name):
489 break
496 break
490
497
491 if group is None:
498 if group is None:
492 log.debug('creating group level: %s group_name: %s',
499 log.debug('creating group level: %s group_name: %s',
493 lvl, group_name)
500 lvl, group_name)
494 group = RepoGroup(group_name, parent)
501 group = RepoGroup(group_name, parent)
495 group.group_description = desc
502 group.group_description = desc
496 group.user = owner
503 group.user = owner
497 sa.add(group)
504 sa.add(group)
498 perm_obj = rgm._create_default_perms(group)
505 perm_obj = rgm._create_default_perms(group)
499 sa.add(perm_obj)
506 sa.add(perm_obj)
500 sa.flush()
507 sa.flush()
501
508
502 parent = group
509 parent = group
503 return group
510 return group
504
511
505
512
506 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
513 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
507 """
514 """
508 maps all repos given in initial_repo_list, non existing repositories
515 maps all repos given in initial_repo_list, non existing repositories
509 are created, if remove_obsolete is True it also checks for db entries
516 are created, if remove_obsolete is True it also checks for db entries
510 that are not in initial_repo_list and removes them.
517 that are not in initial_repo_list and removes them.
511
518
512 :param initial_repo_list: list of repositories found by scanning methods
519 :param initial_repo_list: list of repositories found by scanning methods
513 :param remove_obsolete: check for obsolete entries in database
520 :param remove_obsolete: check for obsolete entries in database
514 """
521 """
515 from rhodecode.model.repo import RepoModel
522 from rhodecode.model.repo import RepoModel
516 from rhodecode.model.scm import ScmModel
523 from rhodecode.model.scm import ScmModel
517 from rhodecode.model.repo_group import RepoGroupModel
524 from rhodecode.model.repo_group import RepoGroupModel
518 from rhodecode.model.settings import SettingsModel
525 from rhodecode.model.settings import SettingsModel
519
526
520 sa = meta.Session()
527 sa = meta.Session()
521 repo_model = RepoModel()
528 repo_model = RepoModel()
522 user = User.get_first_super_admin()
529 user = User.get_first_super_admin()
523 added = []
530 added = []
524
531
525 # creation defaults
532 # creation defaults
526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
533 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
527 enable_statistics = defs.get('repo_enable_statistics')
534 enable_statistics = defs.get('repo_enable_statistics')
528 enable_locking = defs.get('repo_enable_locking')
535 enable_locking = defs.get('repo_enable_locking')
529 enable_downloads = defs.get('repo_enable_downloads')
536 enable_downloads = defs.get('repo_enable_downloads')
530 private = defs.get('repo_private')
537 private = defs.get('repo_private')
531
538
532 for name, repo in initial_repo_list.items():
539 for name, repo in initial_repo_list.items():
533 group = map_groups(name)
540 group = map_groups(name)
534 unicode_name = safe_unicode(name)
541 unicode_name = safe_unicode(name)
535 db_repo = repo_model.get_by_repo_name(unicode_name)
542 db_repo = repo_model.get_by_repo_name(unicode_name)
536 # found repo that is on filesystem not in RhodeCode database
543 # found repo that is on filesystem not in RhodeCode database
537 if not db_repo:
544 if not db_repo:
538 log.info('repository %s not found, creating now', name)
545 log.info('repository %s not found, creating now', name)
539 added.append(name)
546 added.append(name)
540 desc = (repo.description
547 desc = (repo.description
541 if repo.description != 'unknown'
548 if repo.description != 'unknown'
542 else '%s repository' % name)
549 else '%s repository' % name)
543
550
544 db_repo = repo_model._create_repo(
551 db_repo = repo_model._create_repo(
545 repo_name=name,
552 repo_name=name,
546 repo_type=repo.alias,
553 repo_type=repo.alias,
547 description=desc,
554 description=desc,
548 repo_group=getattr(group, 'group_id', None),
555 repo_group=getattr(group, 'group_id', None),
549 owner=user,
556 owner=user,
550 enable_locking=enable_locking,
557 enable_locking=enable_locking,
551 enable_downloads=enable_downloads,
558 enable_downloads=enable_downloads,
552 enable_statistics=enable_statistics,
559 enable_statistics=enable_statistics,
553 private=private,
560 private=private,
554 state=Repository.STATE_CREATED
561 state=Repository.STATE_CREATED
555 )
562 )
556 sa.commit()
563 sa.commit()
557 # we added that repo just now, and make sure we updated server info
564 # we added that repo just now, and make sure we updated server info
558 if db_repo.repo_type == 'git':
565 if db_repo.repo_type == 'git':
559 git_repo = db_repo.scm_instance()
566 git_repo = db_repo.scm_instance()
560 # update repository server-info
567 # update repository server-info
561 log.debug('Running update server info')
568 log.debug('Running update server info')
562 git_repo._update_server_info()
569 git_repo._update_server_info()
563
570
564 db_repo.update_commit_cache()
571 db_repo.update_commit_cache()
565
572
566 config = db_repo._config
573 config = db_repo._config
567 config.set('extensions', 'largefiles', '')
574 config.set('extensions', 'largefiles', '')
568 ScmModel().install_hooks(
575 ScmModel().install_hooks(
569 db_repo.scm_instance(config=config),
576 db_repo.scm_instance(config=config),
570 repo_type=db_repo.repo_type)
577 repo_type=db_repo.repo_type)
571
578
572 removed = []
579 removed = []
573 if remove_obsolete:
580 if remove_obsolete:
574 # remove from database those repositories that are not in the filesystem
581 # remove from database those repositories that are not in the filesystem
575 for repo in sa.query(Repository).all():
582 for repo in sa.query(Repository).all():
576 if repo.repo_name not in initial_repo_list.keys():
583 if repo.repo_name not in initial_repo_list.keys():
577 log.debug("Removing non-existing repository found in db `%s`",
584 log.debug("Removing non-existing repository found in db `%s`",
578 repo.repo_name)
585 repo.repo_name)
579 try:
586 try:
580 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
587 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
581 sa.commit()
588 sa.commit()
582 removed.append(repo.repo_name)
589 removed.append(repo.repo_name)
583 except Exception:
590 except Exception:
584 # don't hold further removals on error
591 # don't hold further removals on error
585 log.error(traceback.format_exc())
592 log.error(traceback.format_exc())
586 sa.rollback()
593 sa.rollback()
587
594
588 def splitter(full_repo_name):
595 def splitter(full_repo_name):
589 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
596 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
590 gr_name = None
597 gr_name = None
591 if len(_parts) == 2:
598 if len(_parts) == 2:
592 gr_name = _parts[0]
599 gr_name = _parts[0]
593 return gr_name
600 return gr_name
594
601
595 initial_repo_group_list = [splitter(x) for x in
602 initial_repo_group_list = [splitter(x) for x in
596 initial_repo_list.keys() if splitter(x)]
603 initial_repo_list.keys() if splitter(x)]
597
604
598 # remove from database those repository groups that are not in the
605 # remove from database those repository groups that are not in the
599 # filesystem due to parent child relationships we need to delete them
606 # filesystem due to parent child relationships we need to delete them
600 # in a specific order of most nested first
607 # in a specific order of most nested first
601 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
608 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
602 nested_sort = lambda gr: len(gr.split('/'))
609 nested_sort = lambda gr: len(gr.split('/'))
603 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
610 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
604 if group_name not in initial_repo_group_list:
611 if group_name not in initial_repo_group_list:
605 repo_group = RepoGroup.get_by_group_name(group_name)
612 repo_group = RepoGroup.get_by_group_name(group_name)
606 if (repo_group.children.all() or
613 if (repo_group.children.all() or
607 not RepoGroupModel().check_exist_filesystem(
614 not RepoGroupModel().check_exist_filesystem(
608 group_name=group_name, exc_on_failure=False)):
615 group_name=group_name, exc_on_failure=False)):
609 continue
616 continue
610
617
611 log.info(
618 log.info(
612 'Removing non-existing repository group found in db `%s`',
619 'Removing non-existing repository group found in db `%s`',
613 group_name)
620 group_name)
614 try:
621 try:
615 RepoGroupModel(sa).delete(group_name, fs_remove=False)
622 RepoGroupModel(sa).delete(group_name, fs_remove=False)
616 sa.commit()
623 sa.commit()
617 removed.append(group_name)
624 removed.append(group_name)
618 except Exception:
625 except Exception:
619 # don't hold further removals on error
626 # don't hold further removals on error
620 log.exception(
627 log.exception(
621 'Unable to remove repository group `%s`',
628 'Unable to remove repository group `%s`',
622 group_name)
629 group_name)
623 sa.rollback()
630 sa.rollback()
624 raise
631 raise
625
632
626 return added, removed
633 return added, removed
627
634
628
635
629 def get_default_cache_settings(settings):
636 def get_default_cache_settings(settings):
630 cache_settings = {}
637 cache_settings = {}
631 for key in settings.keys():
638 for key in settings.keys():
632 for prefix in ['beaker.cache.', 'cache.']:
639 for prefix in ['beaker.cache.', 'cache.']:
633 if key.startswith(prefix):
640 if key.startswith(prefix):
634 name = key.split(prefix)[1].strip()
641 name = key.split(prefix)[1].strip()
635 cache_settings[name] = settings[key].strip()
642 cache_settings[name] = settings[key].strip()
636 return cache_settings
643 return cache_settings
637
644
638
645
639 # set cache regions for beaker so celery can utilise it
646 # set cache regions for beaker so celery can utilise it
640 def add_cache(settings):
647 def add_cache(settings):
641 from rhodecode.lib import caches
648 from rhodecode.lib import caches
642 cache_settings = {'regions': None}
649 cache_settings = {'regions': None}
643 # main cache settings used as default ...
650 # main cache settings used as default ...
644 cache_settings.update(get_default_cache_settings(settings))
651 cache_settings.update(get_default_cache_settings(settings))
645
652
646 if cache_settings['regions']:
653 if cache_settings['regions']:
647 for region in cache_settings['regions'].split(','):
654 for region in cache_settings['regions'].split(','):
648 region = region.strip()
655 region = region.strip()
649 region_settings = {}
656 region_settings = {}
650 for key, value in cache_settings.items():
657 for key, value in cache_settings.items():
651 if key.startswith(region):
658 if key.startswith(region):
652 region_settings[key.split('.')[1]] = value
659 region_settings[key.split('.')[1]] = value
653
660
654 caches.configure_cache_region(
661 caches.configure_cache_region(
655 region, region_settings, cache_settings)
662 region, region_settings, cache_settings)
656
663
657
664
658 def load_rcextensions(root_path):
665 def load_rcextensions(root_path):
659 import rhodecode
666 import rhodecode
660 from rhodecode.config import conf
667 from rhodecode.config import conf
661
668
662 path = os.path.join(root_path, 'rcextensions', '__init__.py')
669 path = os.path.join(root_path, 'rcextensions', '__init__.py')
663 if os.path.isfile(path):
670 if os.path.isfile(path):
664 rcext = create_module('rc', path)
671 rcext = create_module('rc', path)
665 EXT = rhodecode.EXTENSIONS = rcext
672 EXT = rhodecode.EXTENSIONS = rcext
666 log.debug('Found rcextensions now loading %s...', rcext)
673 log.debug('Found rcextensions now loading %s...', rcext)
667
674
668 # Additional mappings that are not present in the pygments lexers
675 # Additional mappings that are not present in the pygments lexers
669 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
676 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
670
677
671 # auto check if the module is not missing any data, set to default if is
678 # auto check if the module is not missing any data, set to default if is
672 # this will help autoupdate new feature of rcext module
679 # this will help autoupdate new feature of rcext module
673 #from rhodecode.config import rcextensions
680 #from rhodecode.config import rcextensions
674 #for k in dir(rcextensions):
681 #for k in dir(rcextensions):
675 # if not k.startswith('_') and not hasattr(EXT, k):
682 # if not k.startswith('_') and not hasattr(EXT, k):
676 # setattr(EXT, k, getattr(rcextensions, k))
683 # setattr(EXT, k, getattr(rcextensions, k))
677
684
678
685
679 def get_custom_lexer(extension):
686 def get_custom_lexer(extension):
680 """
687 """
681 returns a custom lexer if it is defined in rcextensions module, or None
688 returns a custom lexer if it is defined in rcextensions module, or None
682 if there's no custom lexer defined
689 if there's no custom lexer defined
683 """
690 """
684 import rhodecode
691 import rhodecode
685 from pygments import lexers
692 from pygments import lexers
686
693
687 # custom override made by RhodeCode
694 # custom override made by RhodeCode
688 if extension in ['mako']:
695 if extension in ['mako']:
689 return lexers.get_lexer_by_name('html+mako')
696 return lexers.get_lexer_by_name('html+mako')
690
697
691 # check if we didn't define this extension as other lexer
698 # check if we didn't define this extension as other lexer
692 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
699 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
693 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
700 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
694 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
701 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
695 return lexers.get_lexer_by_name(_lexer_name)
702 return lexers.get_lexer_by_name(_lexer_name)
696
703
697
704
698 #==============================================================================
705 #==============================================================================
699 # TEST FUNCTIONS AND CREATORS
706 # TEST FUNCTIONS AND CREATORS
700 #==============================================================================
707 #==============================================================================
701 def create_test_index(repo_location, config):
708 def create_test_index(repo_location, config):
702 """
709 """
703 Makes default test index.
710 Makes default test index.
704 """
711 """
705 import rc_testdata
712 import rc_testdata
706
713
707 rc_testdata.extract_search_index(
714 rc_testdata.extract_search_index(
708 'vcs_search_index', os.path.dirname(config['search.location']))
715 'vcs_search_index', os.path.dirname(config['search.location']))
709
716
710
717
711 def create_test_directory(test_path):
718 def create_test_directory(test_path):
712 """
719 """
713 Create test directory if it doesn't exist.
720 Create test directory if it doesn't exist.
714 """
721 """
715 if not os.path.isdir(test_path):
722 if not os.path.isdir(test_path):
716 log.debug('Creating testdir %s', test_path)
723 log.debug('Creating testdir %s', test_path)
717 os.makedirs(test_path)
724 os.makedirs(test_path)
718
725
719
726
720 def create_test_database(test_path, config):
727 def create_test_database(test_path, config):
721 """
728 """
722 Makes a fresh database.
729 Makes a fresh database.
723 """
730 """
724 from rhodecode.lib.db_manage import DbManage
731 from rhodecode.lib.db_manage import DbManage
725
732
726 # PART ONE create db
733 # PART ONE create db
727 dbconf = config['sqlalchemy.db1.url']
734 dbconf = config['sqlalchemy.db1.url']
728 log.debug('making test db %s', dbconf)
735 log.debug('making test db %s', dbconf)
729
736
730 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
737 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
731 tests=True, cli_args={'force_ask': True})
738 tests=True, cli_args={'force_ask': True})
732 dbmanage.create_tables(override=True)
739 dbmanage.create_tables(override=True)
733 dbmanage.set_db_version()
740 dbmanage.set_db_version()
734 # for tests dynamically set new root paths based on generated content
741 # for tests dynamically set new root paths based on generated content
735 dbmanage.create_settings(dbmanage.config_prompt(test_path))
742 dbmanage.create_settings(dbmanage.config_prompt(test_path))
736 dbmanage.create_default_user()
743 dbmanage.create_default_user()
737 dbmanage.create_test_admin_and_users()
744 dbmanage.create_test_admin_and_users()
738 dbmanage.create_permissions()
745 dbmanage.create_permissions()
739 dbmanage.populate_default_permissions()
746 dbmanage.populate_default_permissions()
740 Session().commit()
747 Session().commit()
741
748
742
749
743 def create_test_repositories(test_path, config):
750 def create_test_repositories(test_path, config):
744 """
751 """
745 Creates test repositories in the temporary directory. Repositories are
752 Creates test repositories in the temporary directory. Repositories are
746 extracted from archives within the rc_testdata package.
753 extracted from archives within the rc_testdata package.
747 """
754 """
748 import rc_testdata
755 import rc_testdata
749 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
756 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
750
757
751 log.debug('making test vcs repositories')
758 log.debug('making test vcs repositories')
752
759
753 idx_path = config['search.location']
760 idx_path = config['search.location']
754 data_path = config['cache_dir']
761 data_path = config['cache_dir']
755
762
756 # clean index and data
763 # clean index and data
757 if idx_path and os.path.exists(idx_path):
764 if idx_path and os.path.exists(idx_path):
758 log.debug('remove %s', idx_path)
765 log.debug('remove %s', idx_path)
759 shutil.rmtree(idx_path)
766 shutil.rmtree(idx_path)
760
767
761 if data_path and os.path.exists(data_path):
768 if data_path and os.path.exists(data_path):
762 log.debug('remove %s', data_path)
769 log.debug('remove %s', data_path)
763 shutil.rmtree(data_path)
770 shutil.rmtree(data_path)
764
771
765 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
772 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
766 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
773 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
767
774
768 # Note: Subversion is in the process of being integrated with the system,
775 # Note: Subversion is in the process of being integrated with the system,
769 # until we have a properly packed version of the test svn repository, this
776 # until we have a properly packed version of the test svn repository, this
770 # tries to copy over the repo from a package "rc_testdata"
777 # tries to copy over the repo from a package "rc_testdata"
771 svn_repo_path = rc_testdata.get_svn_repo_archive()
778 svn_repo_path = rc_testdata.get_svn_repo_archive()
772 with tarfile.open(svn_repo_path) as tar:
779 with tarfile.open(svn_repo_path) as tar:
773 tar.extractall(jn(test_path, SVN_REPO))
780 tar.extractall(jn(test_path, SVN_REPO))
774
781
775
782
776 #==============================================================================
783 #==============================================================================
777 # PASTER COMMANDS
784 # PASTER COMMANDS
778 #==============================================================================
785 #==============================================================================
779 class BasePasterCommand(Command):
786 class BasePasterCommand(Command):
780 """
787 """
781 Abstract Base Class for paster commands.
788 Abstract Base Class for paster commands.
782
789
783 The celery commands are somewhat aggressive about loading
790 The celery commands are somewhat aggressive about loading
784 celery.conf, and since our module sets the `CELERY_LOADER`
791 celery.conf, and since our module sets the `CELERY_LOADER`
785 environment variable to our loader, we have to bootstrap a bit and
792 environment variable to our loader, we have to bootstrap a bit and
786 make sure we've had a chance to load the pylons config off of the
793 make sure we've had a chance to load the pylons config off of the
787 command line, otherwise everything fails.
794 command line, otherwise everything fails.
788 """
795 """
789 min_args = 1
796 min_args = 1
790 min_args_error = "Please provide a paster config file as an argument."
797 min_args_error = "Please provide a paster config file as an argument."
791 takes_config_file = 1
798 takes_config_file = 1
792 requires_config_file = True
799 requires_config_file = True
793
800
794 def notify_msg(self, msg, log=False):
801 def notify_msg(self, msg, log=False):
795 """Make a notification to user, additionally if logger is passed
802 """Make a notification to user, additionally if logger is passed
796 it logs this action using given logger
803 it logs this action using given logger
797
804
798 :param msg: message that will be printed to user
805 :param msg: message that will be printed to user
799 :param log: logging instance, to use to additionally log this message
806 :param log: logging instance, to use to additionally log this message
800
807
801 """
808 """
802 if log and isinstance(log, logging):
809 if log and isinstance(log, logging):
803 log(msg)
810 log(msg)
804
811
805 def run(self, args):
812 def run(self, args):
806 """
813 """
807 Overrides Command.run
814 Overrides Command.run
808
815
809 Checks for a config file argument and loads it.
816 Checks for a config file argument and loads it.
810 """
817 """
811 if len(args) < self.min_args:
818 if len(args) < self.min_args:
812 raise BadCommand(
819 raise BadCommand(
813 self.min_args_error % {'min_args': self.min_args,
820 self.min_args_error % {'min_args': self.min_args,
814 'actual_args': len(args)})
821 'actual_args': len(args)})
815
822
816 # Decrement because we're going to lob off the first argument.
823 # Decrement because we're going to lob off the first argument.
817 # @@ This is hacky
824 # @@ This is hacky
818 self.min_args -= 1
825 self.min_args -= 1
819 self.bootstrap_config(args[0])
826 self.bootstrap_config(args[0])
820 self.update_parser()
827 self.update_parser()
821 return super(BasePasterCommand, self).run(args[1:])
828 return super(BasePasterCommand, self).run(args[1:])
822
829
823 def update_parser(self):
830 def update_parser(self):
824 """
831 """
825 Abstract method. Allows for the class' parser to be updated
832 Abstract method. Allows for the class' parser to be updated
826 before the superclass' `run` method is called. Necessary to
833 before the superclass' `run` method is called. Necessary to
827 allow options/arguments to be passed through to the underlying
834 allow options/arguments to be passed through to the underlying
828 celery command.
835 celery command.
829 """
836 """
830 raise NotImplementedError("Abstract Method.")
837 raise NotImplementedError("Abstract Method.")
831
838
832 def bootstrap_config(self, conf):
839 def bootstrap_config(self, conf):
833 """
840 """
834 Loads the pylons configuration.
841 Loads the pylons configuration.
835 """
842 """
836 from pylons import config as pylonsconfig
843 from pylons import config as pylonsconfig
837
844
838 self.path_to_ini_file = os.path.realpath(conf)
845 self.path_to_ini_file = os.path.realpath(conf)
839 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
846 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
840 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
847 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
841
848
842 def _init_session(self):
849 def _init_session(self):
843 """
850 """
844 Inits SqlAlchemy Session
851 Inits SqlAlchemy Session
845 """
852 """
846 logging.config.fileConfig(self.path_to_ini_file)
853 logging.config.fileConfig(self.path_to_ini_file)
847 from pylons import config
854 from pylons import config
848 from rhodecode.config.utils import initialize_database
855 from rhodecode.config.utils import initialize_database
849
856
850 # get to remove repos !!
857 # get to remove repos !!
851 add_cache(config)
858 add_cache(config)
852 initialize_database(config)
859 initialize_database(config)
853
860
854
861
855 class PartialRenderer(object):
862 class PartialRenderer(object):
856 """
863 """
857 Partial renderer used to render chunks of html used in datagrids
864 Partial renderer used to render chunks of html used in datagrids
858 use like::
865 use like::
859
866
860 _render = PartialRenderer('data_table/_dt_elements.mako')
867 _render = PartialRenderer('data_table/_dt_elements.mako')
861 _render('quick_menu', args, kwargs)
868 _render('quick_menu', args, kwargs)
862 PartialRenderer.h,
869 PartialRenderer.h,
863 c,
870 c,
864 _,
871 _,
865 ungettext
872 ungettext
866 are the template stuff initialized inside and can be re-used later
873 are the template stuff initialized inside and can be re-used later
867
874
868 :param tmpl_name: template path relate to /templates/ dir
875 :param tmpl_name: template path relate to /templates/ dir
869 """
876 """
870
877
871 def __init__(self, tmpl_name):
878 def __init__(self, tmpl_name):
872 import rhodecode
879 import rhodecode
873 from pylons import request, tmpl_context as c
880 from pylons import request, tmpl_context as c
874 from pylons.i18n.translation import _, ungettext
881 from pylons.i18n.translation import _, ungettext
875 from rhodecode.lib import helpers as h
882 from rhodecode.lib import helpers as h
876
883
877 self.tmpl_name = tmpl_name
884 self.tmpl_name = tmpl_name
878 self.rhodecode = rhodecode
885 self.rhodecode = rhodecode
879 self.c = c
886 self.c = c
880 self._ = _
887 self._ = _
881 self.ungettext = ungettext
888 self.ungettext = ungettext
882 self.h = h
889 self.h = h
883 self.request = request
890 self.request = request
884
891
885 def _mako_lookup(self):
892 def _mako_lookup(self):
886 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
893 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
887 return _tmpl_lookup.get_template(self.tmpl_name)
894 return _tmpl_lookup.get_template(self.tmpl_name)
888
895
889 def _update_kwargs_for_render(self, kwargs):
896 def _update_kwargs_for_render(self, kwargs):
890 """
897 """
891 Inject params required for Mako rendering
898 Inject params required for Mako rendering
892 """
899 """
893 _kwargs = {
900 _kwargs = {
894 '_': self._,
901 '_': self._,
895 'h': self.h,
902 'h': self.h,
896 'c': self.c,
903 'c': self.c,
897 'request': self.request,
904 'request': self.request,
898 '_ungettext': self.ungettext,
905 '_ungettext': self.ungettext,
899 }
906 }
900 _kwargs.update(kwargs)
907 _kwargs.update(kwargs)
901 return _kwargs
908 return _kwargs
902
909
903 def _render_with_exc(self, render_func, args, kwargs):
910 def _render_with_exc(self, render_func, args, kwargs):
904 try:
911 try:
905 return render_func.render(*args, **kwargs)
912 return render_func.render(*args, **kwargs)
906 except:
913 except:
907 log.error(exceptions.text_error_template().render())
914 log.error(exceptions.text_error_template().render())
908 raise
915 raise
909
916
910 def _get_template(self, template_obj, def_name):
917 def _get_template(self, template_obj, def_name):
911 if def_name:
918 if def_name:
912 tmpl = template_obj.get_def(def_name)
919 tmpl = template_obj.get_def(def_name)
913 else:
920 else:
914 tmpl = template_obj
921 tmpl = template_obj
915 return tmpl
922 return tmpl
916
923
917 def render(self, def_name, *args, **kwargs):
924 def render(self, def_name, *args, **kwargs):
918 lookup_obj = self._mako_lookup()
925 lookup_obj = self._mako_lookup()
919 tmpl = self._get_template(lookup_obj, def_name=def_name)
926 tmpl = self._get_template(lookup_obj, def_name=def_name)
920 kwargs = self._update_kwargs_for_render(kwargs)
927 kwargs = self._update_kwargs_for_render(kwargs)
921 return self._render_with_exc(tmpl, args, kwargs)
928 return self._render_with_exc(tmpl, args, kwargs)
922
929
923 def __call__(self, tmpl, *args, **kwargs):
930 def __call__(self, tmpl, *args, **kwargs):
924 return self.render(tmpl, *args, **kwargs)
931 return self.render(tmpl, *args, **kwargs)
925
932
926
933
927 def password_changed(auth_user, session):
934 def password_changed(auth_user, session):
928 # Never report password change in case of default user or anonymous user.
935 # Never report password change in case of default user or anonymous user.
929 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
936 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
930 return False
937 return False
931
938
932 password_hash = md5(auth_user.password) if auth_user.password else None
939 password_hash = md5(auth_user.password) if auth_user.password else None
933 rhodecode_user = session.get('rhodecode_user', {})
940 rhodecode_user = session.get('rhodecode_user', {})
934 session_password_hash = rhodecode_user.get('password', '')
941 session_password_hash = rhodecode_user.get('password', '')
935 return password_hash != session_password_hash
942 return password_hash != session_password_hash
936
943
937
944
938 def read_opensource_licenses():
945 def read_opensource_licenses():
939 global _license_cache
946 global _license_cache
940
947
941 if not _license_cache:
948 if not _license_cache:
942 licenses = pkg_resources.resource_string(
949 licenses = pkg_resources.resource_string(
943 'rhodecode', 'config/licenses.json')
950 'rhodecode', 'config/licenses.json')
944 _license_cache = json.loads(licenses)
951 _license_cache = json.loads(licenses)
945
952
946 return _license_cache
953 return _license_cache
947
954
948
955
949 def get_registry(request):
956 def get_registry(request):
950 """
957 """
951 Utility to get the pyramid registry from a request. During migration to
958 Utility to get the pyramid registry from a request. During migration to
952 pyramid we sometimes want to use the pyramid registry from pylons context.
959 pyramid we sometimes want to use the pyramid registry from pylons context.
953 Therefore this utility returns `request.registry` for pyramid requests and
960 Therefore this utility returns `request.registry` for pyramid requests and
954 uses `get_current_registry()` for pylons requests.
961 uses `get_current_registry()` for pylons requests.
955 """
962 """
956 try:
963 try:
957 return request.registry
964 return request.registry
958 except AttributeError:
965 except AttributeError:
959 return get_current_registry()
966 return get_current_registry()
960
967
961
968
962 def generate_platform_uuid():
969 def generate_platform_uuid():
963 """
970 """
964 Generates platform UUID based on it's name
971 Generates platform UUID based on it's name
965 """
972 """
966 import platform
973 import platform
967
974
968 try:
975 try:
969 uuid_list = [platform.platform()]
976 uuid_list = [platform.platform()]
970 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
977 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
971 except Exception as e:
978 except Exception as e:
972 log.error('Failed to generate host uuid: %s' % e)
979 log.error('Failed to generate host uuid: %s' % e)
973 return 'UNDEFINED'
980 return 'UNDEFINED'
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 repository permission model for RhodeCode
23 repository permission model for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 from rhodecode.model import BaseModel
27 from rhodecode.model import BaseModel
28 from rhodecode.model.db import UserRepoToPerm, UserGroupRepoToPerm, \
28 from rhodecode.model.db import UserRepoToPerm, UserGroupRepoToPerm, \
29 Permission
29 Permission
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class RepositoryPermissionModel(BaseModel):
34 class RepositoryPermissionModel(BaseModel):
35
35
36 cls = UserRepoToPerm
36 cls = UserRepoToPerm
37
37
38 def get_user_permission(self, repository, user):
38 def get_user_permission(self, repository, user):
39 repository = self._get_repo(repository)
39 repository = self._get_repo(repository)
40 user = self._get_user(user)
40 user = self._get_user(user)
41
41
42 return UserRepoToPerm.query() \
42 return UserRepoToPerm.query() \
43 .filter(UserRepoToPerm.user == user) \
43 .filter(UserRepoToPerm.user == user) \
44 .filter(UserRepoToPerm.repository == repository) \
44 .filter(UserRepoToPerm.repository == repository) \
45 .scalar()
45 .scalar()
46
46
47 def update_user_permission(self, repository, user, permission):
47 def update_user_permission(self, repository, user, permission):
48 permission = Permission.get_by_key(permission)
48 permission = Permission.get_by_key(permission)
49 current = self.get_user_permission(repository, user)
49 current = self.get_user_permission(repository, user)
50 if current:
50 if current:
51 if not current.permission is permission:
51 if not current.permission is permission:
52 current.permission = permission
52 current.permission = permission
53 else:
53 else:
54 p = UserRepoToPerm()
54 p = UserRepoToPerm()
55 p.user = user
55 p.user = user
56 p.repository = repository
56 p.repository = repository
57 p.permission = permission
57 p.permission = permission
58 self.sa.add(p)
58 self.sa.add(p)
59
59
60 def delete_user_permission(self, repository, user):
60 def delete_user_permission(self, repository, user):
61 current = self.get_user_permission(repository, user)
61 current = self.get_user_permission(repository, user)
62 if current:
62 if current:
63 self.sa.delete(current)
63 self.sa.delete(current)
64
64
65 def get_users_group_permission(self, repository, users_group):
65 def get_users_group_permission(self, repository, users_group):
66 return UserGroupRepoToPerm.query() \
66 return UserGroupRepoToPerm.query() \
67 .filter(UserGroupRepoToPerm.users_group == users_group) \
67 .filter(UserGroupRepoToPerm.users_group == users_group) \
68 .filter(UserGroupRepoToPerm.repository == repository) \
68 .filter(UserGroupRepoToPerm.repository == repository) \
69 .scalar()
69 .scalar()
70
70
71 def update_users_group_permission(self, repository, users_group,
71 def update_user_group_permission(self, repository, users_group,
72 permission):
72 permission):
73 permission = Permission.get_by_key(permission)
73 permission = Permission.get_by_key(permission)
74 current = self.get_users_group_permission(repository, users_group)
74 current = self.get_users_group_permission(repository, users_group)
75 if current:
75 if current:
76 if not current.permission is permission:
76 if not current.permission is permission:
77 current.permission = permission
77 current.permission = permission
78 else:
78 else:
79 p = UserGroupRepoToPerm()
79 p = UserGroupRepoToPerm()
80 p.users_group = users_group
80 p.users_group = users_group
81 p.repository = repository
81 p.repository = repository
82 p.permission = permission
82 p.permission = permission
83 self.sa.add(p)
83 self.sa.add(p)
84
84
85 def delete_users_group_permission(self, repository, users_group):
85 def delete_users_group_permission(self, repository, users_group):
86 current = self.get_users_group_permission(repository, users_group)
86 current = self.get_users_group_permission(repository, users_group)
87 if current:
87 if current:
88 self.sa.delete(current)
88 self.sa.delete(current)
89
89
90 def update_or_delete_user_permission(self, repository, user, permission):
90 def update_or_delete_user_permission(self, repository, user, permission):
91 if permission:
91 if permission:
92 self.update_user_permission(repository, user, permission)
92 self.update_user_permission(repository, user, permission)
93 else:
93 else:
94 self.delete_user_permission(repository, user)
94 self.delete_user_permission(repository, user)
95
95
96 def update_or_delete_users_group_permission(self, repository, user_group,
96 def update_or_delete_users_group_permission(self, repository, user_group,
97 permission):
97 permission):
98 if permission:
98 if permission:
99 self.update_users_group_permission(repository, user_group,
99 self.update_user_group_permission(repository, user_group,
100 permission)
100 permission)
101 else:
101 else:
102 self.delete_users_group_permission(repository, user_group)
102 self.delete_users_group_permission(repository, user_group)
@@ -1,623 +1,637 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
22 """
23 user group model for RhodeCode
24 """
25
26
27 import logging
21 import logging
28 import traceback
22 import traceback
29
23
30 from rhodecode.lib.utils2 import safe_str, safe_unicode
24 from rhodecode.lib.utils2 import safe_str, safe_unicode
31 from rhodecode.lib.exceptions import (
25 from rhodecode.lib.exceptions import (
32 UserGroupAssignedException, RepoGroupAssignmentError)
26 UserGroupAssignedException, RepoGroupAssignmentError)
33 from rhodecode.lib.utils2 import (
27 from rhodecode.lib.utils2 import (
34 get_current_rhodecode_user, action_logger_generic)
28 get_current_rhodecode_user, action_logger_generic)
35 from rhodecode.model import BaseModel
29 from rhodecode.model import BaseModel
36 from rhodecode.model.scm import UserGroupList
30 from rhodecode.model.scm import UserGroupList
37 from rhodecode.model.db import (
31 from rhodecode.model.db import (
38 true, func, User, UserGroupMember, UserGroup,
32 true, func, User, UserGroupMember, UserGroup,
39 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
40 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
41
35
42
36
43 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
44
38
45
39
46 class UserGroupModel(BaseModel):
40 class UserGroupModel(BaseModel):
47
41
48 cls = UserGroup
42 cls = UserGroup
49
43
50 def _get_user_group(self, user_group):
44 def _get_user_group(self, user_group):
51 return self._get_instance(UserGroup, user_group,
45 return self._get_instance(UserGroup, user_group,
52 callback=UserGroup.get_by_group_name)
46 callback=UserGroup.get_by_group_name)
53
47
54 def _create_default_perms(self, user_group):
48 def _create_default_perms(self, user_group):
55 # create default permission
49 # create default permission
56 default_perm = 'usergroup.read'
50 default_perm = 'usergroup.read'
57 def_user = User.get_default_user()
51 def_user = User.get_default_user()
58 for p in def_user.user_perms:
52 for p in def_user.user_perms:
59 if p.permission.permission_name.startswith('usergroup.'):
53 if p.permission.permission_name.startswith('usergroup.'):
60 default_perm = p.permission.permission_name
54 default_perm = p.permission.permission_name
61 break
55 break
62
56
63 user_group_to_perm = UserUserGroupToPerm()
57 user_group_to_perm = UserUserGroupToPerm()
64 user_group_to_perm.permission = Permission.get_by_key(default_perm)
58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
65
59
66 user_group_to_perm.user_group = user_group
60 user_group_to_perm.user_group = user_group
67 user_group_to_perm.user_id = def_user.user_id
61 user_group_to_perm.user_id = def_user.user_id
68 return user_group_to_perm
62 return user_group_to_perm
69
63
70 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
64 def update_permissions(
71 perm_deletions=None, check_perms=True, cur_user=None):
65 self, user_group, perm_additions=None, perm_updates=None,
66 perm_deletions=None, check_perms=True, cur_user=None):
67
72 from rhodecode.lib.auth import HasUserGroupPermissionAny
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
73 if not perm_additions:
69 if not perm_additions:
74 perm_additions = []
70 perm_additions = []
75 if not perm_updates:
71 if not perm_updates:
76 perm_updates = []
72 perm_updates = []
77 if not perm_deletions:
73 if not perm_deletions:
78 perm_deletions = []
74 perm_deletions = []
79
75
80 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
81
77
78 changes = {
79 'added': [],
80 'updated': [],
81 'deleted': []
82 }
82 # update permissions
83 # update permissions
83 for member_id, perm, member_type in perm_updates:
84 for member_id, perm, member_type in perm_updates:
84 member_id = int(member_id)
85 member_id = int(member_id)
85 if member_type == 'user':
86 if member_type == 'user':
87 member_name = User.get(member_id).username
86 # this updates existing one
88 # this updates existing one
87 self.grant_user_permission(
89 self.grant_user_permission(
88 user_group=user_group, user=member_id, perm=perm
90 user_group=user_group, user=member_id, perm=perm
89 )
91 )
90 else:
92 else:
91 # check if we have permissions to alter this usergroup
93 # check if we have permissions to alter this usergroup
92 member_name = UserGroup.get(member_id).users_group_name
94 member_name = UserGroup.get(member_id).users_group_name
93 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
95 if not check_perms or HasUserGroupPermissionAny(
96 *req_perms)(member_name, user=cur_user):
94 self.grant_user_group_permission(
97 self.grant_user_group_permission(
95 target_user_group=user_group, user_group=member_id, perm=perm
98 target_user_group=user_group, user_group=member_id, perm=perm)
96 )
99
100 changes['updated'].append({'type': member_type, 'id': member_id,
101 'name': member_name, 'new_perm': perm})
97
102
98 # set new permissions
103 # set new permissions
99 for member_id, perm, member_type in perm_additions:
104 for member_id, perm, member_type in perm_additions:
100 member_id = int(member_id)
105 member_id = int(member_id)
101 if member_type == 'user':
106 if member_type == 'user':
107 member_name = User.get(member_id).username
102 self.grant_user_permission(
108 self.grant_user_permission(
103 user_group=user_group, user=member_id, perm=perm
109 user_group=user_group, user=member_id, perm=perm)
104 )
105 else:
110 else:
106 # check if we have permissions to alter this usergroup
111 # check if we have permissions to alter this usergroup
107 member_name = UserGroup.get(member_id).users_group_name
112 member_name = UserGroup.get(member_id).users_group_name
108 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
113 if not check_perms or HasUserGroupPermissionAny(
114 *req_perms)(member_name, user=cur_user):
109 self.grant_user_group_permission(
115 self.grant_user_group_permission(
110 target_user_group=user_group, user_group=member_id, perm=perm
116 target_user_group=user_group, user_group=member_id, perm=perm)
111 )
117
118 changes['added'].append({'type': member_type, 'id': member_id,
119 'name': member_name, 'new_perm': perm})
112
120
113 # delete permissions
121 # delete permissions
114 for member_id, perm, member_type in perm_deletions:
122 for member_id, perm, member_type in perm_deletions:
115 member_id = int(member_id)
123 member_id = int(member_id)
116 if member_type == 'user':
124 if member_type == 'user':
125 member_name = User.get(member_id).username
117 self.revoke_user_permission(user_group=user_group, user=member_id)
126 self.revoke_user_permission(user_group=user_group, user=member_id)
118 else:
127 else:
119 # check if we have permissions to alter this usergroup
128 # check if we have permissions to alter this usergroup
120 member_name = UserGroup.get(member_id).users_group_name
129 member_name = UserGroup.get(member_id).users_group_name
121 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
130 if not check_perms or HasUserGroupPermissionAny(
131 *req_perms)(member_name, user=cur_user):
122 self.revoke_user_group_permission(
132 self.revoke_user_group_permission(
123 target_user_group=user_group, user_group=member_id
133 target_user_group=user_group, user_group=member_id)
124 )
134
135 changes['deleted'].append({'type': member_type, 'id': member_id,
136 'name': member_name, 'new_perm': perm})
137 return changes
125
138
126 def get(self, user_group_id, cache=False):
139 def get(self, user_group_id, cache=False):
127 return UserGroup.get(user_group_id)
140 return UserGroup.get(user_group_id)
128
141
129 def get_group(self, user_group):
142 def get_group(self, user_group):
130 return self._get_user_group(user_group)
143 return self._get_user_group(user_group)
131
144
132 def get_by_name(self, name, cache=False, case_insensitive=False):
145 def get_by_name(self, name, cache=False, case_insensitive=False):
133 return UserGroup.get_by_group_name(name, cache, case_insensitive)
146 return UserGroup.get_by_group_name(name, cache, case_insensitive)
134
147
135 def create(self, name, description, owner, active=True, group_data=None):
148 def create(self, name, description, owner, active=True, group_data=None):
136 try:
149 try:
137 new_user_group = UserGroup()
150 new_user_group = UserGroup()
138 new_user_group.user = self._get_user(owner)
151 new_user_group.user = self._get_user(owner)
139 new_user_group.users_group_name = name
152 new_user_group.users_group_name = name
140 new_user_group.user_group_description = description
153 new_user_group.user_group_description = description
141 new_user_group.users_group_active = active
154 new_user_group.users_group_active = active
142 if group_data:
155 if group_data:
143 new_user_group.group_data = group_data
156 new_user_group.group_data = group_data
144 self.sa.add(new_user_group)
157 self.sa.add(new_user_group)
145 perm_obj = self._create_default_perms(new_user_group)
158 perm_obj = self._create_default_perms(new_user_group)
146 self.sa.add(perm_obj)
159 self.sa.add(perm_obj)
147
160
148 self.grant_user_permission(user_group=new_user_group,
161 self.grant_user_permission(user_group=new_user_group,
149 user=owner, perm='usergroup.admin')
162 user=owner, perm='usergroup.admin')
150
163
151 return new_user_group
164 return new_user_group
152 except Exception:
165 except Exception:
153 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
154 raise
167 raise
155
168
156 def _get_memberships_for_user_ids(self, user_group, user_id_list):
169 def _get_memberships_for_user_ids(self, user_group, user_id_list):
157 members = []
170 members = []
158 for user_id in user_id_list:
171 for user_id in user_id_list:
159 member = self._get_membership(user_group.users_group_id, user_id)
172 member = self._get_membership(user_group.users_group_id, user_id)
160 members.append(member)
173 members.append(member)
161 return members
174 return members
162
175
163 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
176 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
164 current_members = user_group.members or []
177 current_members = user_group.members or []
165 current_members_ids = [m.user.user_id for m in current_members]
178 current_members_ids = [m.user.user_id for m in current_members]
166
179
167 added_members = [
180 added_members = [
168 user_id for user_id in user_id_list
181 user_id for user_id in user_id_list
169 if user_id not in current_members_ids]
182 if user_id not in current_members_ids]
170 if user_id_list == []:
183 if user_id_list == []:
171 # all members were deleted
184 # all members were deleted
172 deleted_members = current_members_ids
185 deleted_members = current_members_ids
173 else:
186 else:
174 deleted_members = [
187 deleted_members = [
175 user_id for user_id in current_members_ids
188 user_id for user_id in current_members_ids
176 if user_id not in user_id_list]
189 if user_id not in user_id_list]
177
190
178 return added_members, deleted_members
191 return added_members, deleted_members
179
192
180 def _set_users_as_members(self, user_group, user_ids):
193 def _set_users_as_members(self, user_group, user_ids):
181 user_group.members = []
194 user_group.members = []
182 self.sa.flush()
195 self.sa.flush()
183 members = self._get_memberships_for_user_ids(
196 members = self._get_memberships_for_user_ids(
184 user_group, user_ids)
197 user_group, user_ids)
185 user_group.members = members
198 user_group.members = members
186 self.sa.add(user_group)
199 self.sa.add(user_group)
187
200
188 def _update_members_from_user_ids(self, user_group, user_ids):
201 def _update_members_from_user_ids(self, user_group, user_ids):
189 added, removed = self._get_added_and_removed_user_ids(
202 added, removed = self._get_added_and_removed_user_ids(
190 user_group, user_ids)
203 user_group, user_ids)
191 self._set_users_as_members(user_group, user_ids)
204 self._set_users_as_members(user_group, user_ids)
192 self._log_user_changes('added to', user_group, added)
205 self._log_user_changes('added to', user_group, added)
193 self._log_user_changes('removed from', user_group, removed)
206 self._log_user_changes('removed from', user_group, removed)
194 return added, removed
207 return added, removed
195
208
196 def _clean_members_data(self, members_data):
209 def _clean_members_data(self, members_data):
197 if not members_data:
210 if not members_data:
198 members_data = []
211 members_data = []
199
212
200 members = []
213 members = []
201 for user in members_data:
214 for user in members_data:
202 uid = int(user['member_user_id'])
215 uid = int(user['member_user_id'])
203 if uid not in members and user['type'] in ['new', 'existing']:
216 if uid not in members and user['type'] in ['new', 'existing']:
204 members.append(uid)
217 members.append(uid)
205 return members
218 return members
206
219
207 def update(self, user_group, form_data):
220 def update(self, user_group, form_data):
208 user_group = self._get_user_group(user_group)
221 user_group = self._get_user_group(user_group)
209 if 'users_group_name' in form_data:
222 if 'users_group_name' in form_data:
210 user_group.users_group_name = form_data['users_group_name']
223 user_group.users_group_name = form_data['users_group_name']
211 if 'users_group_active' in form_data:
224 if 'users_group_active' in form_data:
212 user_group.users_group_active = form_data['users_group_active']
225 user_group.users_group_active = form_data['users_group_active']
213 if 'user_group_description' in form_data:
226 if 'user_group_description' in form_data:
214 user_group.user_group_description = form_data[
227 user_group.user_group_description = form_data[
215 'user_group_description']
228 'user_group_description']
216
229
217 # handle owner change
230 # handle owner change
218 if 'user' in form_data:
231 if 'user' in form_data:
219 owner = form_data['user']
232 owner = form_data['user']
220 if isinstance(owner, basestring):
233 if isinstance(owner, basestring):
221 owner = User.get_by_username(form_data['user'])
234 owner = User.get_by_username(form_data['user'])
222
235
223 if not isinstance(owner, User):
236 if not isinstance(owner, User):
224 raise ValueError(
237 raise ValueError(
225 'invalid owner for user group: %s' % form_data['user'])
238 'invalid owner for user group: %s' % form_data['user'])
226
239
227 user_group.user = owner
240 user_group.user = owner
228
241
229 added_user_ids = []
242 added_user_ids = []
230 removed_user_ids = []
243 removed_user_ids = []
231 if 'users_group_members' in form_data:
244 if 'users_group_members' in form_data:
232 members_id_list = self._clean_members_data(
245 members_id_list = self._clean_members_data(
233 form_data['users_group_members'])
246 form_data['users_group_members'])
234 added_user_ids, removed_user_ids = \
247 added_user_ids, removed_user_ids = \
235 self._update_members_from_user_ids(user_group, members_id_list)
248 self._update_members_from_user_ids(user_group, members_id_list)
236
249
237 self.sa.add(user_group)
250 self.sa.add(user_group)
238 return user_group, added_user_ids, removed_user_ids
251 return user_group, added_user_ids, removed_user_ids
239
252
240 def delete(self, user_group, force=False):
253 def delete(self, user_group, force=False):
241 """
254 """
242 Deletes repository group, unless force flag is used
255 Deletes repository group, unless force flag is used
243 raises exception if there are members in that group, else deletes
256 raises exception if there are members in that group, else deletes
244 group and users
257 group and users
245
258
246 :param user_group:
259 :param user_group:
247 :param force:
260 :param force:
248 """
261 """
249 user_group = self._get_user_group(user_group)
262 user_group = self._get_user_group(user_group)
263 if not user_group:
264 return
265
250 try:
266 try:
251 # check if this group is not assigned to repo
267 # check if this group is not assigned to repo
252 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
268 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
253 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
269 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
254 # check if this group is not assigned to repo
270 # check if this group is not assigned to repo
255 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
271 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
256 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
272 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
257
273
258 if (assigned_to_repo or assigned_to_repo_group) and not force:
274 if (assigned_to_repo or assigned_to_repo_group) and not force:
259 assigned = ','.join(map(safe_str,
275 assigned = ','.join(map(safe_str,
260 assigned_to_repo+assigned_to_repo_group))
276 assigned_to_repo+assigned_to_repo_group))
261
277
262 raise UserGroupAssignedException(
278 raise UserGroupAssignedException(
263 'UserGroup assigned to %s' % (assigned,))
279 'UserGroup assigned to %s' % (assigned,))
264 self.sa.delete(user_group)
280 self.sa.delete(user_group)
265 except Exception:
281 except Exception:
266 log.error(traceback.format_exc())
282 log.error(traceback.format_exc())
267 raise
283 raise
268
284
269 def _log_user_changes(self, action, user_group, user_or_users):
285 def _log_user_changes(self, action, user_group, user_or_users):
270 users = user_or_users
286 users = user_or_users
271 if not isinstance(users, (list, tuple)):
287 if not isinstance(users, (list, tuple)):
272 users = [users]
288 users = [users]
273 rhodecode_user = get_current_rhodecode_user()
289 rhodecode_user = get_current_rhodecode_user()
274 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
290 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
275 group_name = user_group.users_group_name
291 group_name = user_group.users_group_name
276
292
277 for user_or_user_id in users:
293 for user_or_user_id in users:
278 user = self._get_user(user_or_user_id)
294 user = self._get_user(user_or_user_id)
279 log_text = 'User {user} {action} {group}'.format(
295 log_text = 'User {user} {action} {group}'.format(
280 action=action, user=user.username, group=group_name)
296 action=action, user=user.username, group=group_name)
281 log.info('Logging action: {0} by {1} ip:{2}'.format(
297 log.info('Logging action: {0} by {1} ip:{2}'.format(
282 log_text, rhodecode_user, ipaddr))
298 log_text, rhodecode_user, ipaddr))
283
299
284 def _find_user_in_group(self, user, user_group):
300 def _find_user_in_group(self, user, user_group):
285 user_group_member = None
301 user_group_member = None
286 for m in user_group.members:
302 for m in user_group.members:
287 if m.user_id == user.user_id:
303 if m.user_id == user.user_id:
288 # Found this user's membership row
304 # Found this user's membership row
289 user_group_member = m
305 user_group_member = m
290 break
306 break
291
307
292 return user_group_member
308 return user_group_member
293
309
294 def _get_membership(self, user_group_id, user_id):
310 def _get_membership(self, user_group_id, user_id):
295 user_group_member = UserGroupMember(user_group_id, user_id)
311 user_group_member = UserGroupMember(user_group_id, user_id)
296 return user_group_member
312 return user_group_member
297
313
298 def add_user_to_group(self, user_group, user):
314 def add_user_to_group(self, user_group, user):
299 user_group = self._get_user_group(user_group)
315 user_group = self._get_user_group(user_group)
300 user = self._get_user(user)
316 user = self._get_user(user)
301 user_member = self._find_user_in_group(user, user_group)
317 user_member = self._find_user_in_group(user, user_group)
302 if user_member:
318 if user_member:
303 # user already in the group, skip
319 # user already in the group, skip
304 return True
320 return True
305
321
306 member = self._get_membership(
322 member = self._get_membership(
307 user_group.users_group_id, user.user_id)
323 user_group.users_group_id, user.user_id)
308 user_group.members.append(member)
324 user_group.members.append(member)
309
325
310 try:
326 try:
311 self.sa.add(member)
327 self.sa.add(member)
312 except Exception:
328 except Exception:
313 # what could go wrong here?
329 # what could go wrong here?
314 log.error(traceback.format_exc())
330 log.error(traceback.format_exc())
315 raise
331 raise
316
332
317 self._log_user_changes('added to', user_group, user)
333 self._log_user_changes('added to', user_group, user)
318 return member
334 return member
319
335
320 def remove_user_from_group(self, user_group, user):
336 def remove_user_from_group(self, user_group, user):
321 user_group = self._get_user_group(user_group)
337 user_group = self._get_user_group(user_group)
322 user = self._get_user(user)
338 user = self._get_user(user)
323 user_group_member = self._find_user_in_group(user, user_group)
339 user_group_member = self._find_user_in_group(user, user_group)
324
340
325 if not user_group_member:
341 if not user_group_member:
326 # User isn't in that group
342 # User isn't in that group
327 return False
343 return False
328
344
329 try:
345 try:
330 self.sa.delete(user_group_member)
346 self.sa.delete(user_group_member)
331 except Exception:
347 except Exception:
332 log.error(traceback.format_exc())
348 log.error(traceback.format_exc())
333 raise
349 raise
334
350
335 self._log_user_changes('removed from', user_group, user)
351 self._log_user_changes('removed from', user_group, user)
336 return True
352 return True
337
353
338 def has_perm(self, user_group, perm):
354 def has_perm(self, user_group, perm):
339 user_group = self._get_user_group(user_group)
355 user_group = self._get_user_group(user_group)
340 perm = self._get_perm(perm)
356 perm = self._get_perm(perm)
341
357
342 return UserGroupToPerm.query()\
358 return UserGroupToPerm.query()\
343 .filter(UserGroupToPerm.users_group == user_group)\
359 .filter(UserGroupToPerm.users_group == user_group)\
344 .filter(UserGroupToPerm.permission == perm).scalar() is not None
360 .filter(UserGroupToPerm.permission == perm).scalar() is not None
345
361
346 def grant_perm(self, user_group, perm):
362 def grant_perm(self, user_group, perm):
347 user_group = self._get_user_group(user_group)
363 user_group = self._get_user_group(user_group)
348 perm = self._get_perm(perm)
364 perm = self._get_perm(perm)
349
365
350 # if this permission is already granted skip it
366 # if this permission is already granted skip it
351 _perm = UserGroupToPerm.query()\
367 _perm = UserGroupToPerm.query()\
352 .filter(UserGroupToPerm.users_group == user_group)\
368 .filter(UserGroupToPerm.users_group == user_group)\
353 .filter(UserGroupToPerm.permission == perm)\
369 .filter(UserGroupToPerm.permission == perm)\
354 .scalar()
370 .scalar()
355 if _perm:
371 if _perm:
356 return
372 return
357
373
358 new = UserGroupToPerm()
374 new = UserGroupToPerm()
359 new.users_group = user_group
375 new.users_group = user_group
360 new.permission = perm
376 new.permission = perm
361 self.sa.add(new)
377 self.sa.add(new)
362 return new
378 return new
363
379
364 def revoke_perm(self, user_group, perm):
380 def revoke_perm(self, user_group, perm):
365 user_group = self._get_user_group(user_group)
381 user_group = self._get_user_group(user_group)
366 perm = self._get_perm(perm)
382 perm = self._get_perm(perm)
367
383
368 obj = UserGroupToPerm.query()\
384 obj = UserGroupToPerm.query()\
369 .filter(UserGroupToPerm.users_group == user_group)\
385 .filter(UserGroupToPerm.users_group == user_group)\
370 .filter(UserGroupToPerm.permission == perm).scalar()
386 .filter(UserGroupToPerm.permission == perm).scalar()
371 if obj:
387 if obj:
372 self.sa.delete(obj)
388 self.sa.delete(obj)
373
389
374 def grant_user_permission(self, user_group, user, perm):
390 def grant_user_permission(self, user_group, user, perm):
375 """
391 """
376 Grant permission for user on given user group, or update
392 Grant permission for user on given user group, or update
377 existing one if found
393 existing one if found
378
394
379 :param user_group: Instance of UserGroup, users_group_id,
395 :param user_group: Instance of UserGroup, users_group_id,
380 or users_group_name
396 or users_group_name
381 :param user: Instance of User, user_id or username
397 :param user: Instance of User, user_id or username
382 :param perm: Instance of Permission, or permission_name
398 :param perm: Instance of Permission, or permission_name
383 """
399 """
384
400
385 user_group = self._get_user_group(user_group)
401 user_group = self._get_user_group(user_group)
386 user = self._get_user(user)
402 user = self._get_user(user)
387 permission = self._get_perm(perm)
403 permission = self._get_perm(perm)
388
404
389 # check if we have that permission already
405 # check if we have that permission already
390 obj = self.sa.query(UserUserGroupToPerm)\
406 obj = self.sa.query(UserUserGroupToPerm)\
391 .filter(UserUserGroupToPerm.user == user)\
407 .filter(UserUserGroupToPerm.user == user)\
392 .filter(UserUserGroupToPerm.user_group == user_group)\
408 .filter(UserUserGroupToPerm.user_group == user_group)\
393 .scalar()
409 .scalar()
394 if obj is None:
410 if obj is None:
395 # create new !
411 # create new !
396 obj = UserUserGroupToPerm()
412 obj = UserUserGroupToPerm()
397 obj.user_group = user_group
413 obj.user_group = user_group
398 obj.user = user
414 obj.user = user
399 obj.permission = permission
415 obj.permission = permission
400 self.sa.add(obj)
416 self.sa.add(obj)
401 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
417 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
402 action_logger_generic(
418 action_logger_generic(
403 'granted permission: {} to user: {} on usergroup: {}'.format(
419 'granted permission: {} to user: {} on usergroup: {}'.format(
404 perm, user, user_group), namespace='security.usergroup')
420 perm, user, user_group), namespace='security.usergroup')
405
421
406 return obj
422 return obj
407
423
408 def revoke_user_permission(self, user_group, user):
424 def revoke_user_permission(self, user_group, user):
409 """
425 """
410 Revoke permission for user on given user group
426 Revoke permission for user on given user group
411
427
412 :param user_group: Instance of UserGroup, users_group_id,
428 :param user_group: Instance of UserGroup, users_group_id,
413 or users_group name
429 or users_group name
414 :param user: Instance of User, user_id or username
430 :param user: Instance of User, user_id or username
415 """
431 """
416
432
417 user_group = self._get_user_group(user_group)
433 user_group = self._get_user_group(user_group)
418 user = self._get_user(user)
434 user = self._get_user(user)
419
435
420 obj = self.sa.query(UserUserGroupToPerm)\
436 obj = self.sa.query(UserUserGroupToPerm)\
421 .filter(UserUserGroupToPerm.user == user)\
437 .filter(UserUserGroupToPerm.user == user)\
422 .filter(UserUserGroupToPerm.user_group == user_group)\
438 .filter(UserUserGroupToPerm.user_group == user_group)\
423 .scalar()
439 .scalar()
424 if obj:
440 if obj:
425 self.sa.delete(obj)
441 self.sa.delete(obj)
426 log.debug('Revoked perm on %s on %s', user_group, user)
442 log.debug('Revoked perm on %s on %s', user_group, user)
427 action_logger_generic(
443 action_logger_generic(
428 'revoked permission from user: {} on usergroup: {}'.format(
444 'revoked permission from user: {} on usergroup: {}'.format(
429 user, user_group), namespace='security.usergroup')
445 user, user_group), namespace='security.usergroup')
430
446
431 def grant_user_group_permission(self, target_user_group, user_group, perm):
447 def grant_user_group_permission(self, target_user_group, user_group, perm):
432 """
448 """
433 Grant user group permission for given target_user_group
449 Grant user group permission for given target_user_group
434
450
435 :param target_user_group:
451 :param target_user_group:
436 :param user_group:
452 :param user_group:
437 :param perm:
453 :param perm:
438 """
454 """
439 target_user_group = self._get_user_group(target_user_group)
455 target_user_group = self._get_user_group(target_user_group)
440 user_group = self._get_user_group(user_group)
456 user_group = self._get_user_group(user_group)
441 permission = self._get_perm(perm)
457 permission = self._get_perm(perm)
442 # forbid assigning same user group to itself
458 # forbid assigning same user group to itself
443 if target_user_group == user_group:
459 if target_user_group == user_group:
444 raise RepoGroupAssignmentError('target repo:%s cannot be '
460 raise RepoGroupAssignmentError('target repo:%s cannot be '
445 'assigned to itself' % target_user_group)
461 'assigned to itself' % target_user_group)
446
462
447 # check if we have that permission already
463 # check if we have that permission already
448 obj = self.sa.query(UserGroupUserGroupToPerm)\
464 obj = self.sa.query(UserGroupUserGroupToPerm)\
449 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
465 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
450 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
466 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
451 .scalar()
467 .scalar()
452 if obj is None:
468 if obj is None:
453 # create new !
469 # create new !
454 obj = UserGroupUserGroupToPerm()
470 obj = UserGroupUserGroupToPerm()
455 obj.user_group = user_group
471 obj.user_group = user_group
456 obj.target_user_group = target_user_group
472 obj.target_user_group = target_user_group
457 obj.permission = permission
473 obj.permission = permission
458 self.sa.add(obj)
474 self.sa.add(obj)
459 log.debug(
475 log.debug(
460 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
476 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
461 action_logger_generic(
477 action_logger_generic(
462 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
478 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
463 perm, user_group, target_user_group),
479 perm, user_group, target_user_group),
464 namespace='security.usergroup')
480 namespace='security.usergroup')
465
481
466 return obj
482 return obj
467
483
468 def revoke_user_group_permission(self, target_user_group, user_group):
484 def revoke_user_group_permission(self, target_user_group, user_group):
469 """
485 """
470 Revoke user group permission for given target_user_group
486 Revoke user group permission for given target_user_group
471
487
472 :param target_user_group:
488 :param target_user_group:
473 :param user_group:
489 :param user_group:
474 """
490 """
475 target_user_group = self._get_user_group(target_user_group)
491 target_user_group = self._get_user_group(target_user_group)
476 user_group = self._get_user_group(user_group)
492 user_group = self._get_user_group(user_group)
477
493
478 obj = self.sa.query(UserGroupUserGroupToPerm)\
494 obj = self.sa.query(UserGroupUserGroupToPerm)\
479 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
495 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
480 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
496 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
481 .scalar()
497 .scalar()
482 if obj:
498 if obj:
483 self.sa.delete(obj)
499 self.sa.delete(obj)
484 log.debug(
500 log.debug(
485 'Revoked perm on %s on %s', target_user_group, user_group)
501 'Revoked perm on %s on %s', target_user_group, user_group)
486 action_logger_generic(
502 action_logger_generic(
487 'revoked permission from usergroup: {} on usergroup: {}'.format(
503 'revoked permission from usergroup: {} on usergroup: {}'.format(
488 user_group, target_user_group),
504 user_group, target_user_group),
489 namespace='security.repogroup')
505 namespace='security.repogroup')
490
506
491 def enforce_groups(self, user, groups, extern_type=None):
507 def enforce_groups(self, user, groups, extern_type=None):
492 user = self._get_user(user)
508 user = self._get_user(user)
493 log.debug('Enforcing groups %s on user %s', groups, user)
509 log.debug('Enforcing groups %s on user %s', groups, user)
494 current_groups = user.group_member
510 current_groups = user.group_member
495 # find the external created groups
511 # find the external created groups
496 externals = [x.users_group for x in current_groups
512 externals = [x.users_group for x in current_groups
497 if 'extern_type' in x.users_group.group_data]
513 if 'extern_type' in x.users_group.group_data]
498
514
499 # calculate from what groups user should be removed
515 # calculate from what groups user should be removed
500 # externals that are not in groups
516 # externals that are not in groups
501 for gr in externals:
517 for gr in externals:
502 if gr.users_group_name not in groups:
518 if gr.users_group_name not in groups:
503 log.debug('Removing user %s from user group %s', user, gr)
519 log.debug('Removing user %s from user group %s', user, gr)
504 self.remove_user_from_group(gr, user)
520 self.remove_user_from_group(gr, user)
505
521
506 # now we calculate in which groups user should be == groups params
522 # now we calculate in which groups user should be == groups params
507 owner = User.get_first_super_admin().username
523 owner = User.get_first_super_admin().username
508 for gr in set(groups):
524 for gr in set(groups):
509 existing_group = UserGroup.get_by_group_name(gr)
525 existing_group = UserGroup.get_by_group_name(gr)
510 if not existing_group:
526 if not existing_group:
511 desc = 'Automatically created from plugin:%s' % extern_type
527 desc = 'Automatically created from plugin:%s' % extern_type
512 # we use first admin account to set the owner of the group
528 # we use first admin account to set the owner of the group
513 existing_group = UserGroupModel().create(
529 existing_group = UserGroupModel().create(
514 gr, desc, owner, group_data={'extern_type': extern_type})
530 gr, desc, owner, group_data={'extern_type': extern_type})
515
531
516 # we can only add users to special groups created via plugins
532 # we can only add users to special groups created via plugins
517 managed = 'extern_type' in existing_group.group_data
533 managed = 'extern_type' in existing_group.group_data
518 if managed:
534 if managed:
519 log.debug('Adding user %s to user group %s', user, gr)
535 log.debug('Adding user %s to user group %s', user, gr)
520 UserGroupModel().add_user_to_group(existing_group, user)
536 UserGroupModel().add_user_to_group(existing_group, user)
521 else:
537 else:
522 log.debug('Skipping addition to group %s since it is '
538 log.debug('Skipping addition to group %s since it is '
523 'not set to be automatically synchronized' % gr)
539 'not set to be automatically synchronized' % gr)
524
540
525 def change_groups(self, user, groups):
541 def change_groups(self, user, groups):
526 """
542 """
527 This method changes user group assignment
543 This method changes user group assignment
528 :param user: User
544 :param user: User
529 :param groups: array of UserGroupModel
545 :param groups: array of UserGroupModel
530 :return:
531 """
546 """
532 user = self._get_user(user)
547 user = self._get_user(user)
533 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
548 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
534 current_groups = user.group_member
549 current_groups = user.group_member
535 current_groups = [x.users_group for x in current_groups]
550 current_groups = [x.users_group for x in current_groups]
536
551
537 # calculate from what groups user should be removed/add
552 # calculate from what groups user should be removed/add
538 groups = set(groups)
553 groups = set(groups)
539 current_groups = set(current_groups)
554 current_groups = set(current_groups)
540
555
541 groups_to_remove = current_groups - groups
556 groups_to_remove = current_groups - groups
542 groups_to_add = groups - current_groups
557 groups_to_add = groups - current_groups
543
558
544 for gr in groups_to_remove:
559 for gr in groups_to_remove:
545 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
560 log.debug('Removing user %s from user group %s',
561 user.username, gr.users_group_name)
546 self.remove_user_from_group(gr.users_group_name, user.username)
562 self.remove_user_from_group(gr.users_group_name, user.username)
547 for gr in groups_to_add:
563 for gr in groups_to_add:
548 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
564 log.debug('Adding user %s to user group %s',
549 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
565 user.username, gr.users_group_name)
566 UserGroupModel().add_user_to_group(
567 gr.users_group_name, user.username)
550
568
551 def _serialize_user_group(self, user_group):
569 def _serialize_user_group(self, user_group):
552 import rhodecode.lib.helpers as h
570 import rhodecode.lib.helpers as h
553 return {
571 return {
554 'id': user_group.users_group_id,
572 'id': user_group.users_group_id,
555 # TODO: marcink figure out a way to generate the url for the
573 # TODO: marcink figure out a way to generate the url for the
556 # icon
574 # icon
557 'icon_link': '',
575 'icon_link': '',
558 'value_display': 'Group: %s (%d members)' % (
576 'value_display': 'Group: %s (%d members)' % (
559 user_group.users_group_name, len(user_group.members),),
577 user_group.users_group_name, len(user_group.members),),
560 'value': user_group.users_group_name,
578 'value': user_group.users_group_name,
561 'description': user_group.user_group_description,
579 'description': user_group.user_group_description,
562 'owner': user_group.user.username,
580 'owner': user_group.user.username,
563
581
564 'owner_icon': h.gravatar_url(user_group.user.email, 30),
582 'owner_icon': h.gravatar_url(user_group.user.email, 30),
565 'value_display_owner': h.person(user_group.user.email),
583 'value_display_owner': h.person(user_group.user.email),
566
584
567 'value_type': 'user_group',
585 'value_type': 'user_group',
568 'active': user_group.users_group_active,
586 'active': user_group.users_group_active,
569 }
587 }
570
588
571 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
589 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
572 expand_groups=False):
590 expand_groups=False):
573 query = self.sa.query(UserGroup)
591 query = self.sa.query(UserGroup)
574 if only_active:
592 if only_active:
575 query = query.filter(UserGroup.users_group_active == true())
593 query = query.filter(UserGroup.users_group_active == true())
576
594
577 if name_contains:
595 if name_contains:
578 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
596 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
579 query = query.filter(
597 query = query.filter(
580 UserGroup.users_group_name.ilike(ilike_expression))\
598 UserGroup.users_group_name.ilike(ilike_expression))\
581 .order_by(func.length(UserGroup.users_group_name))\
599 .order_by(func.length(UserGroup.users_group_name))\
582 .order_by(UserGroup.users_group_name)
600 .order_by(UserGroup.users_group_name)
583
601
584 query = query.limit(limit)
602 query = query.limit(limit)
585 user_groups = query.all()
603 user_groups = query.all()
586 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
604 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
587 user_groups = UserGroupList(user_groups, perm_set=perm_set)
605 user_groups = UserGroupList(user_groups, perm_set=perm_set)
588
606
589 # store same serialize method to extract data from User
607 # store same serialize method to extract data from User
590 from rhodecode.model.user import UserModel
608 from rhodecode.model.user import UserModel
591 serialize_user = UserModel()._serialize_user
609 serialize_user = UserModel()._serialize_user
592
610
593 _groups = []
611 _groups = []
594 for group in user_groups:
612 for group in user_groups:
595 entry = self._serialize_user_group(group)
613 entry = self._serialize_user_group(group)
596 if expand_groups:
614 if expand_groups:
597 expanded_members = []
615 expanded_members = []
598 for member in group.members:
616 for member in group.members:
599 expanded_members.append(serialize_user(member.user))
617 expanded_members.append(serialize_user(member.user))
600 entry['members'] = expanded_members
618 entry['members'] = expanded_members
601 _groups.append(entry)
619 _groups.append(entry)
602 return _groups
620 return _groups
603
621
604 @staticmethod
622 @staticmethod
605 def get_user_groups_as_dict(user_group):
623 def get_user_groups_as_dict(user_group):
606 import rhodecode.lib.helpers as h
624 import rhodecode.lib.helpers as h
607
625
608 data = {
626 data = {
609 'users_group_id': user_group.users_group_id,
627 'users_group_id': user_group.users_group_id,
610 'group_name': user_group.users_group_name,
628 'group_name': user_group.users_group_name,
611 'group_description': user_group.user_group_description,
629 'group_description': user_group.user_group_description,
612 'active': user_group.users_group_active,
630 'active': user_group.users_group_active,
613 "owner": user_group.user.username,
631 "owner": user_group.user.username,
614 'owner_icon': h.gravatar_url(user_group.user.email, 30),
632 'owner_icon': h.gravatar_url(user_group.user.email, 30),
615 "owner_data": {
633 "owner_data": {
616 'owner': user_group.user.username,
634 'owner': user_group.user.username,
617 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
635 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
618 }
636 }
619 return data
637 return data
620
621
622
623
@@ -1,1122 +1,1122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Set of generic validators
22 Set of generic validators
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 from collections import defaultdict
28 from collections import defaultdict
29
29
30 import formencode
30 import formencode
31 import ipaddress
31 import ipaddress
32 from formencode.validators import (
32 from formencode.validators import (
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 )
35 )
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.sql.expression import true
37 from sqlalchemy.sql.expression import true
38 from sqlalchemy.util import OrderedSet
38 from sqlalchemy.util import OrderedSet
39 from webhelpers.pylonslib.secure_form import authentication_token
39 from webhelpers.pylonslib.secure_form import authentication_token
40
40
41 from rhodecode.authentication import (
41 from rhodecode.authentication import (
42 legacy_plugin_prefix, _import_legacy_plugin)
42 legacy_plugin_prefix, _import_legacy_plugin)
43 from rhodecode.authentication.base import loadplugin
43 from rhodecode.authentication.base import loadplugin
44 from rhodecode.config.routing import ADMIN_PREFIX
44 from rhodecode.config.routing import ADMIN_PREFIX
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 # silence warnings and pylint
55 # silence warnings and pylint
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class _Missing(object):
62 class _Missing(object):
63 pass
63 pass
64
64
65 Missing = _Missing()
65 Missing = _Missing()
66
66
67
67
68 class StateObj(object):
68 class StateObj(object):
69 """
69 """
70 this is needed to translate the messages using _() in validators
70 this is needed to translate the messages using _() in validators
71 """
71 """
72 _ = staticmethod(_)
72 _ = staticmethod(_)
73
73
74
74
75 def M(self, key, state=None, **kwargs):
75 def M(self, key, state=None, **kwargs):
76 """
76 """
77 returns string from self.message based on given key,
77 returns string from self.message based on given key,
78 passed kw params are used to substitute %(named)s params inside
78 passed kw params are used to substitute %(named)s params inside
79 translated strings
79 translated strings
80
80
81 :param msg:
81 :param msg:
82 :param state:
82 :param state:
83 """
83 """
84 if state is None:
84 if state is None:
85 state = StateObj()
85 state = StateObj()
86 else:
86 else:
87 state._ = staticmethod(_)
87 state._ = staticmethod(_)
88 # inject validator into state object
88 # inject validator into state object
89 return self.message(key, state, **kwargs)
89 return self.message(key, state, **kwargs)
90
90
91
91
92 def UniqueList(convert=None):
92 def UniqueList(convert=None):
93 class _UniqueList(formencode.FancyValidator):
93 class _UniqueList(formencode.FancyValidator):
94 """
94 """
95 Unique List !
95 Unique List !
96 """
96 """
97 messages = {
97 messages = {
98 'empty': _(u'Value cannot be an empty list'),
98 'empty': _(u'Value cannot be an empty list'),
99 'missing_value': _(u'Value cannot be an empty list'),
99 'missing_value': _(u'Value cannot be an empty list'),
100 }
100 }
101
101
102 def _to_python(self, value, state):
102 def _to_python(self, value, state):
103 ret_val = []
103 ret_val = []
104
104
105 def make_unique(value):
105 def make_unique(value):
106 seen = []
106 seen = []
107 return [c for c in value if not (c in seen or seen.append(c))]
107 return [c for c in value if not (c in seen or seen.append(c))]
108
108
109 if isinstance(value, list):
109 if isinstance(value, list):
110 ret_val = make_unique(value)
110 ret_val = make_unique(value)
111 elif isinstance(value, set):
111 elif isinstance(value, set):
112 ret_val = make_unique(list(value))
112 ret_val = make_unique(list(value))
113 elif isinstance(value, tuple):
113 elif isinstance(value, tuple):
114 ret_val = make_unique(list(value))
114 ret_val = make_unique(list(value))
115 elif value is None:
115 elif value is None:
116 ret_val = []
116 ret_val = []
117 else:
117 else:
118 ret_val = [value]
118 ret_val = [value]
119
119
120 if convert:
120 if convert:
121 ret_val = map(convert, ret_val)
121 ret_val = map(convert, ret_val)
122 return ret_val
122 return ret_val
123
123
124 def empty_value(self, value):
124 def empty_value(self, value):
125 return []
125 return []
126
126
127 return _UniqueList
127 return _UniqueList
128
128
129
129
130 def UniqueListFromString():
130 def UniqueListFromString():
131 class _UniqueListFromString(UniqueList()):
131 class _UniqueListFromString(UniqueList()):
132 def _to_python(self, value, state):
132 def _to_python(self, value, state):
133 if isinstance(value, basestring):
133 if isinstance(value, basestring):
134 value = aslist(value, ',')
134 value = aslist(value, ',')
135 return super(_UniqueListFromString, self)._to_python(value, state)
135 return super(_UniqueListFromString, self)._to_python(value, state)
136 return _UniqueListFromString
136 return _UniqueListFromString
137
137
138
138
139 def ValidSvnPattern(section, repo_name=None):
139 def ValidSvnPattern(section, repo_name=None):
140 class _validator(formencode.validators.FancyValidator):
140 class _validator(formencode.validators.FancyValidator):
141 messages = {
141 messages = {
142 'pattern_exists': _(u'Pattern already exists'),
142 'pattern_exists': _(u'Pattern already exists'),
143 }
143 }
144
144
145 def validate_python(self, value, state):
145 def validate_python(self, value, state):
146 if not value:
146 if not value:
147 return
147 return
148 model = VcsSettingsModel(repo=repo_name)
148 model = VcsSettingsModel(repo=repo_name)
149 ui_settings = model.get_svn_patterns(section=section)
149 ui_settings = model.get_svn_patterns(section=section)
150 for entry in ui_settings:
150 for entry in ui_settings:
151 if value == entry.value:
151 if value == entry.value:
152 msg = M(self, 'pattern_exists', state)
152 msg = M(self, 'pattern_exists', state)
153 raise formencode.Invalid(msg, value, state)
153 raise formencode.Invalid(msg, value, state)
154 return _validator
154 return _validator
155
155
156
156
157 def ValidUsername(edit=False, old_data={}):
157 def ValidUsername(edit=False, old_data={}):
158 class _validator(formencode.validators.FancyValidator):
158 class _validator(formencode.validators.FancyValidator):
159 messages = {
159 messages = {
160 'username_exists': _(u'Username "%(username)s" already exists'),
160 'username_exists': _(u'Username "%(username)s" already exists'),
161 'system_invalid_username':
161 'system_invalid_username':
162 _(u'Username "%(username)s" is forbidden'),
162 _(u'Username "%(username)s" is forbidden'),
163 'invalid_username':
163 'invalid_username':
164 _(u'Username may only contain alphanumeric characters '
164 _(u'Username may only contain alphanumeric characters '
165 u'underscores, periods or dashes and must begin with '
165 u'underscores, periods or dashes and must begin with '
166 u'alphanumeric character or underscore')
166 u'alphanumeric character or underscore')
167 }
167 }
168
168
169 def validate_python(self, value, state):
169 def validate_python(self, value, state):
170 if value in ['default', 'new_user']:
170 if value in ['default', 'new_user']:
171 msg = M(self, 'system_invalid_username', state, username=value)
171 msg = M(self, 'system_invalid_username', state, username=value)
172 raise formencode.Invalid(msg, value, state)
172 raise formencode.Invalid(msg, value, state)
173 # check if user is unique
173 # check if user is unique
174 old_un = None
174 old_un = None
175 if edit:
175 if edit:
176 old_un = User.get(old_data.get('user_id')).username
176 old_un = User.get(old_data.get('user_id')).username
177
177
178 if old_un != value or not edit:
178 if old_un != value or not edit:
179 if User.get_by_username(value, case_insensitive=True):
179 if User.get_by_username(value, case_insensitive=True):
180 msg = M(self, 'username_exists', state, username=value)
180 msg = M(self, 'username_exists', state, username=value)
181 raise formencode.Invalid(msg, value, state)
181 raise formencode.Invalid(msg, value, state)
182
182
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 is None):
184 is None):
185 msg = M(self, 'invalid_username', state)
185 msg = M(self, 'invalid_username', state)
186 raise formencode.Invalid(msg, value, state)
186 raise formencode.Invalid(msg, value, state)
187 return _validator
187 return _validator
188
188
189
189
190 def ValidRegex(msg=None):
190 def ValidRegex(msg=None):
191 class _validator(formencode.validators.Regex):
191 class _validator(formencode.validators.Regex):
192 messages = {'invalid': msg or _(u'The input is not valid')}
192 messages = {'invalid': msg or _(u'The input is not valid')}
193 return _validator
193 return _validator
194
194
195
195
196 def ValidRepoUser(allow_disabled=False):
196 def ValidRepoUser(allow_disabled=False):
197 class _validator(formencode.validators.FancyValidator):
197 class _validator(formencode.validators.FancyValidator):
198 messages = {
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 'disabled_username': _(u'Username %(username)s is disabled')
201 }
201 }
202
202
203 def validate_python(self, value, state):
203 def validate_python(self, value, state):
204 try:
204 try:
205 user = User.query().filter(User.username == value).one()
205 user = User.query().filter(User.username == value).one()
206 except Exception:
206 except Exception:
207 msg = M(self, 'invalid_username', state, username=value)
207 msg = M(self, 'invalid_username', state, username=value)
208 raise formencode.Invalid(
208 raise formencode.Invalid(
209 msg, value, state, error_dict={'username': msg}
209 msg, value, state, error_dict={'username': msg}
210 )
210 )
211 if user and (not allow_disabled and not user.active):
211 if user and (not allow_disabled and not user.active):
212 msg = M(self, 'disabled_username', state, username=value)
212 msg = M(self, 'disabled_username', state, username=value)
213 raise formencode.Invalid(
213 raise formencode.Invalid(
214 msg, value, state, error_dict={'username': msg}
214 msg, value, state, error_dict={'username': msg}
215 )
215 )
216
216
217 return _validator
217 return _validator
218
218
219
219
220 def ValidUserGroup(edit=False, old_data={}):
220 def ValidUserGroup(edit=False, old_data={}):
221 class _validator(formencode.validators.FancyValidator):
221 class _validator(formencode.validators.FancyValidator):
222 messages = {
222 messages = {
223 'invalid_group': _(u'Invalid user group name'),
223 'invalid_group': _(u'Invalid user group name'),
224 'group_exist': _(u'User group "%(usergroup)s" already exists'),
224 'group_exist': _(u'User group `%(usergroup)s` already exists'),
225 'invalid_usergroup_name':
225 'invalid_usergroup_name':
226 _(u'user group name may only contain alphanumeric '
226 _(u'user group name may only contain alphanumeric '
227 u'characters underscores, periods or dashes and must begin '
227 u'characters underscores, periods or dashes and must begin '
228 u'with alphanumeric character')
228 u'with alphanumeric character')
229 }
229 }
230
230
231 def validate_python(self, value, state):
231 def validate_python(self, value, state):
232 if value in ['default']:
232 if value in ['default']:
233 msg = M(self, 'invalid_group', state)
233 msg = M(self, 'invalid_group', state)
234 raise formencode.Invalid(
234 raise formencode.Invalid(
235 msg, value, state, error_dict={'users_group_name': msg}
235 msg, value, state, error_dict={'users_group_name': msg}
236 )
236 )
237 # check if group is unique
237 # check if group is unique
238 old_ugname = None
238 old_ugname = None
239 if edit:
239 if edit:
240 old_id = old_data.get('users_group_id')
240 old_id = old_data.get('users_group_id')
241 old_ugname = UserGroup.get(old_id).users_group_name
241 old_ugname = UserGroup.get(old_id).users_group_name
242
242
243 if old_ugname != value or not edit:
243 if old_ugname != value or not edit:
244 is_existing_group = UserGroup.get_by_group_name(
244 is_existing_group = UserGroup.get_by_group_name(
245 value, case_insensitive=True)
245 value, case_insensitive=True)
246 if is_existing_group:
246 if is_existing_group:
247 msg = M(self, 'group_exist', state, usergroup=value)
247 msg = M(self, 'group_exist', state, usergroup=value)
248 raise formencode.Invalid(
248 raise formencode.Invalid(
249 msg, value, state, error_dict={'users_group_name': msg}
249 msg, value, state, error_dict={'users_group_name': msg}
250 )
250 )
251
251
252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
253 msg = M(self, 'invalid_usergroup_name', state)
253 msg = M(self, 'invalid_usergroup_name', state)
254 raise formencode.Invalid(
254 raise formencode.Invalid(
255 msg, value, state, error_dict={'users_group_name': msg}
255 msg, value, state, error_dict={'users_group_name': msg}
256 )
256 )
257
257
258 return _validator
258 return _validator
259
259
260
260
261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
262 class _validator(formencode.validators.FancyValidator):
262 class _validator(formencode.validators.FancyValidator):
263 messages = {
263 messages = {
264 'group_parent_id': _(u'Cannot assign this group as parent'),
264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 u'already exists'),
267 u'already exists'),
268 'permission_denied': _(u"no permission to store repository group"
268 'permission_denied': _(u"no permission to store repository group"
269 u"in this location"),
269 u"in this location"),
270 'permission_denied_root': _(
270 'permission_denied_root': _(
271 u"no permission to store repository group "
271 u"no permission to store repository group "
272 u"in root location")
272 u"in root location")
273 }
273 }
274
274
275 def _to_python(self, value, state):
275 def _to_python(self, value, state):
276 group_name = repo_name_slug(value.get('group_name', ''))
276 group_name = repo_name_slug(value.get('group_name', ''))
277 group_parent_id = safe_int(value.get('group_parent_id'))
277 group_parent_id = safe_int(value.get('group_parent_id'))
278 gr = RepoGroup.get(group_parent_id)
278 gr = RepoGroup.get(group_parent_id)
279 if gr:
279 if gr:
280 parent_group_path = gr.full_path
280 parent_group_path = gr.full_path
281 # value needs to be aware of group name in order to check
281 # value needs to be aware of group name in order to check
282 # db key This is an actual just the name to store in the
282 # db key This is an actual just the name to store in the
283 # database
283 # database
284 group_name_full = (
284 group_name_full = (
285 parent_group_path + RepoGroup.url_sep() + group_name)
285 parent_group_path + RepoGroup.url_sep() + group_name)
286 else:
286 else:
287 group_name_full = group_name
287 group_name_full = group_name
288
288
289 value['group_name'] = group_name
289 value['group_name'] = group_name
290 value['group_name_full'] = group_name_full
290 value['group_name_full'] = group_name_full
291 value['group_parent_id'] = group_parent_id
291 value['group_parent_id'] = group_parent_id
292 return value
292 return value
293
293
294 def validate_python(self, value, state):
294 def validate_python(self, value, state):
295
295
296 old_group_name = None
296 old_group_name = None
297 group_name = value.get('group_name')
297 group_name = value.get('group_name')
298 group_name_full = value.get('group_name_full')
298 group_name_full = value.get('group_name_full')
299 group_parent_id = safe_int(value.get('group_parent_id'))
299 group_parent_id = safe_int(value.get('group_parent_id'))
300 if group_parent_id == -1:
300 if group_parent_id == -1:
301 group_parent_id = None
301 group_parent_id = None
302
302
303 group_obj = RepoGroup.get(old_data.get('group_id'))
303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 parent_group_changed = False
304 parent_group_changed = False
305 if edit:
305 if edit:
306 old_group_name = group_obj.group_name
306 old_group_name = group_obj.group_name
307 old_group_parent_id = group_obj.group_parent_id
307 old_group_parent_id = group_obj.group_parent_id
308
308
309 if group_parent_id != old_group_parent_id:
309 if group_parent_id != old_group_parent_id:
310 parent_group_changed = True
310 parent_group_changed = True
311
311
312 # TODO: mikhail: the following if statement is not reached
312 # TODO: mikhail: the following if statement is not reached
313 # since group_parent_id's OneOf validation fails before.
313 # since group_parent_id's OneOf validation fails before.
314 # Can be removed.
314 # Can be removed.
315
315
316 # check against setting a parent of self
316 # check against setting a parent of self
317 parent_of_self = (
317 parent_of_self = (
318 old_data['group_id'] == group_parent_id
318 old_data['group_id'] == group_parent_id
319 if group_parent_id else False
319 if group_parent_id else False
320 )
320 )
321 if parent_of_self:
321 if parent_of_self:
322 msg = M(self, 'group_parent_id', state)
322 msg = M(self, 'group_parent_id', state)
323 raise formencode.Invalid(
323 raise formencode.Invalid(
324 msg, value, state, error_dict={'group_parent_id': msg}
324 msg, value, state, error_dict={'group_parent_id': msg}
325 )
325 )
326
326
327 # group we're moving current group inside
327 # group we're moving current group inside
328 child_group = None
328 child_group = None
329 if group_parent_id:
329 if group_parent_id:
330 child_group = RepoGroup.query().filter(
330 child_group = RepoGroup.query().filter(
331 RepoGroup.group_id == group_parent_id).scalar()
331 RepoGroup.group_id == group_parent_id).scalar()
332
332
333 # do a special check that we cannot move a group to one of
333 # do a special check that we cannot move a group to one of
334 # it's children
334 # it's children
335 if edit and child_group:
335 if edit and child_group:
336 parents = [x.group_id for x in child_group.parents]
336 parents = [x.group_id for x in child_group.parents]
337 move_to_children = old_data['group_id'] in parents
337 move_to_children = old_data['group_id'] in parents
338 if move_to_children:
338 if move_to_children:
339 msg = M(self, 'group_parent_id', state)
339 msg = M(self, 'group_parent_id', state)
340 raise formencode.Invalid(
340 raise formencode.Invalid(
341 msg, value, state, error_dict={'group_parent_id': msg})
341 msg, value, state, error_dict={'group_parent_id': msg})
342
342
343 # Check if we have permission to store in the parent.
343 # Check if we have permission to store in the parent.
344 # Only check if the parent group changed.
344 # Only check if the parent group changed.
345 if parent_group_changed:
345 if parent_group_changed:
346 if child_group is None:
346 if child_group is None:
347 if not can_create_in_root:
347 if not can_create_in_root:
348 msg = M(self, 'permission_denied_root', state)
348 msg = M(self, 'permission_denied_root', state)
349 raise formencode.Invalid(
349 raise formencode.Invalid(
350 msg, value, state,
350 msg, value, state,
351 error_dict={'group_parent_id': msg})
351 error_dict={'group_parent_id': msg})
352 else:
352 else:
353 valid = HasRepoGroupPermissionAny('group.admin')
353 valid = HasRepoGroupPermissionAny('group.admin')
354 forbidden = not valid(
354 forbidden = not valid(
355 child_group.group_name, 'can create group validator')
355 child_group.group_name, 'can create group validator')
356 if forbidden:
356 if forbidden:
357 msg = M(self, 'permission_denied', state)
357 msg = M(self, 'permission_denied', state)
358 raise formencode.Invalid(
358 raise formencode.Invalid(
359 msg, value, state,
359 msg, value, state,
360 error_dict={'group_parent_id': msg})
360 error_dict={'group_parent_id': msg})
361
361
362 # if we change the name or it's new group, check for existing names
362 # if we change the name or it's new group, check for existing names
363 # or repositories with the same name
363 # or repositories with the same name
364 if old_group_name != group_name_full or not edit:
364 if old_group_name != group_name_full or not edit:
365 # check group
365 # check group
366 gr = RepoGroup.get_by_group_name(group_name_full)
366 gr = RepoGroup.get_by_group_name(group_name_full)
367 if gr:
367 if gr:
368 msg = M(self, 'group_exists', state, group_name=group_name)
368 msg = M(self, 'group_exists', state, group_name=group_name)
369 raise formencode.Invalid(
369 raise formencode.Invalid(
370 msg, value, state, error_dict={'group_name': msg})
370 msg, value, state, error_dict={'group_name': msg})
371
371
372 # check for same repo
372 # check for same repo
373 repo = Repository.get_by_repo_name(group_name_full)
373 repo = Repository.get_by_repo_name(group_name_full)
374 if repo:
374 if repo:
375 msg = M(self, 'repo_exists', state, group_name=group_name)
375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 raise formencode.Invalid(
376 raise formencode.Invalid(
377 msg, value, state, error_dict={'group_name': msg})
377 msg, value, state, error_dict={'group_name': msg})
378
378
379 return _validator
379 return _validator
380
380
381
381
382 def ValidPassword():
382 def ValidPassword():
383 class _validator(formencode.validators.FancyValidator):
383 class _validator(formencode.validators.FancyValidator):
384 messages = {
384 messages = {
385 'invalid_password':
385 'invalid_password':
386 _(u'Invalid characters (non-ascii) in password')
386 _(u'Invalid characters (non-ascii) in password')
387 }
387 }
388
388
389 def validate_python(self, value, state):
389 def validate_python(self, value, state):
390 try:
390 try:
391 (value or '').decode('ascii')
391 (value or '').decode('ascii')
392 except UnicodeError:
392 except UnicodeError:
393 msg = M(self, 'invalid_password', state)
393 msg = M(self, 'invalid_password', state)
394 raise formencode.Invalid(msg, value, state,)
394 raise formencode.Invalid(msg, value, state,)
395 return _validator
395 return _validator
396
396
397
397
398 def ValidOldPassword(username):
398 def ValidOldPassword(username):
399 class _validator(formencode.validators.FancyValidator):
399 class _validator(formencode.validators.FancyValidator):
400 messages = {
400 messages = {
401 'invalid_password': _(u'Invalid old password')
401 'invalid_password': _(u'Invalid old password')
402 }
402 }
403
403
404 def validate_python(self, value, state):
404 def validate_python(self, value, state):
405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
406 if not authenticate(username, value, '', HTTP_TYPE):
406 if not authenticate(username, value, '', HTTP_TYPE):
407 msg = M(self, 'invalid_password', state)
407 msg = M(self, 'invalid_password', state)
408 raise formencode.Invalid(
408 raise formencode.Invalid(
409 msg, value, state, error_dict={'current_password': msg}
409 msg, value, state, error_dict={'current_password': msg}
410 )
410 )
411 return _validator
411 return _validator
412
412
413
413
414 def ValidPasswordsMatch(
414 def ValidPasswordsMatch(
415 passwd='new_password', passwd_confirmation='password_confirmation'):
415 passwd='new_password', passwd_confirmation='password_confirmation'):
416 class _validator(formencode.validators.FancyValidator):
416 class _validator(formencode.validators.FancyValidator):
417 messages = {
417 messages = {
418 'password_mismatch': _(u'Passwords do not match'),
418 'password_mismatch': _(u'Passwords do not match'),
419 }
419 }
420
420
421 def validate_python(self, value, state):
421 def validate_python(self, value, state):
422
422
423 pass_val = value.get('password') or value.get(passwd)
423 pass_val = value.get('password') or value.get(passwd)
424 if pass_val != value[passwd_confirmation]:
424 if pass_val != value[passwd_confirmation]:
425 msg = M(self, 'password_mismatch', state)
425 msg = M(self, 'password_mismatch', state)
426 raise formencode.Invalid(
426 raise formencode.Invalid(
427 msg, value, state,
427 msg, value, state,
428 error_dict={passwd: msg, passwd_confirmation: msg}
428 error_dict={passwd: msg, passwd_confirmation: msg}
429 )
429 )
430 return _validator
430 return _validator
431
431
432
432
433 def ValidAuth():
433 def ValidAuth():
434 class _validator(formencode.validators.FancyValidator):
434 class _validator(formencode.validators.FancyValidator):
435 messages = {
435 messages = {
436 'invalid_password': _(u'invalid password'),
436 'invalid_password': _(u'invalid password'),
437 'invalid_username': _(u'invalid user name'),
437 'invalid_username': _(u'invalid user name'),
438 'disabled_account': _(u'Your account is disabled')
438 'disabled_account': _(u'Your account is disabled')
439 }
439 }
440
440
441 def validate_python(self, value, state):
441 def validate_python(self, value, state):
442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
443
443
444 password = value['password']
444 password = value['password']
445 username = value['username']
445 username = value['username']
446
446
447 if not authenticate(username, password, '', HTTP_TYPE,
447 if not authenticate(username, password, '', HTTP_TYPE,
448 skip_missing=True):
448 skip_missing=True):
449 user = User.get_by_username(username)
449 user = User.get_by_username(username)
450 if user and not user.active:
450 if user and not user.active:
451 log.warning('user %s is disabled', username)
451 log.warning('user %s is disabled', username)
452 msg = M(self, 'disabled_account', state)
452 msg = M(self, 'disabled_account', state)
453 raise formencode.Invalid(
453 raise formencode.Invalid(
454 msg, value, state, error_dict={'username': msg}
454 msg, value, state, error_dict={'username': msg}
455 )
455 )
456 else:
456 else:
457 log.warning('user `%s` failed to authenticate', username)
457 log.warning('user `%s` failed to authenticate', username)
458 msg = M(self, 'invalid_username', state)
458 msg = M(self, 'invalid_username', state)
459 msg2 = M(self, 'invalid_password', state)
459 msg2 = M(self, 'invalid_password', state)
460 raise formencode.Invalid(
460 raise formencode.Invalid(
461 msg, value, state,
461 msg, value, state,
462 error_dict={'username': msg, 'password': msg2}
462 error_dict={'username': msg, 'password': msg2}
463 )
463 )
464 return _validator
464 return _validator
465
465
466
466
467 def ValidAuthToken():
467 def ValidAuthToken():
468 class _validator(formencode.validators.FancyValidator):
468 class _validator(formencode.validators.FancyValidator):
469 messages = {
469 messages = {
470 'invalid_token': _(u'Token mismatch')
470 'invalid_token': _(u'Token mismatch')
471 }
471 }
472
472
473 def validate_python(self, value, state):
473 def validate_python(self, value, state):
474 if value != authentication_token():
474 if value != authentication_token():
475 msg = M(self, 'invalid_token', state)
475 msg = M(self, 'invalid_token', state)
476 raise formencode.Invalid(msg, value, state)
476 raise formencode.Invalid(msg, value, state)
477 return _validator
477 return _validator
478
478
479
479
480 def ValidRepoName(edit=False, old_data={}):
480 def ValidRepoName(edit=False, old_data={}):
481 class _validator(formencode.validators.FancyValidator):
481 class _validator(formencode.validators.FancyValidator):
482 messages = {
482 messages = {
483 'invalid_repo_name':
483 'invalid_repo_name':
484 _(u'Repository name %(repo)s is disallowed'),
484 _(u'Repository name %(repo)s is disallowed'),
485 # top level
485 # top level
486 'repository_exists': _(u'Repository with name %(repo)s '
486 'repository_exists': _(u'Repository with name %(repo)s '
487 u'already exists'),
487 u'already exists'),
488 'group_exists': _(u'Repository group with name "%(repo)s" '
488 'group_exists': _(u'Repository group with name "%(repo)s" '
489 u'already exists'),
489 u'already exists'),
490 # inside a group
490 # inside a group
491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
492 u'exists in group "%(group)s"'),
492 u'exists in group "%(group)s"'),
493 'group_in_group_exists': _(
493 'group_in_group_exists': _(
494 u'Repository group with name "%(repo)s" '
494 u'Repository group with name "%(repo)s" '
495 u'exists in group "%(group)s"'),
495 u'exists in group "%(group)s"'),
496 }
496 }
497
497
498 def _to_python(self, value, state):
498 def _to_python(self, value, state):
499 repo_name = repo_name_slug(value.get('repo_name', ''))
499 repo_name = repo_name_slug(value.get('repo_name', ''))
500 repo_group = value.get('repo_group')
500 repo_group = value.get('repo_group')
501 if repo_group:
501 if repo_group:
502 gr = RepoGroup.get(repo_group)
502 gr = RepoGroup.get(repo_group)
503 group_path = gr.full_path
503 group_path = gr.full_path
504 group_name = gr.group_name
504 group_name = gr.group_name
505 # value needs to be aware of group name in order to check
505 # value needs to be aware of group name in order to check
506 # db key This is an actual just the name to store in the
506 # db key This is an actual just the name to store in the
507 # database
507 # database
508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
509 else:
509 else:
510 group_name = group_path = ''
510 group_name = group_path = ''
511 repo_name_full = repo_name
511 repo_name_full = repo_name
512
512
513 value['repo_name'] = repo_name
513 value['repo_name'] = repo_name
514 value['repo_name_full'] = repo_name_full
514 value['repo_name_full'] = repo_name_full
515 value['group_path'] = group_path
515 value['group_path'] = group_path
516 value['group_name'] = group_name
516 value['group_name'] = group_name
517 return value
517 return value
518
518
519 def validate_python(self, value, state):
519 def validate_python(self, value, state):
520
520
521 repo_name = value.get('repo_name')
521 repo_name = value.get('repo_name')
522 repo_name_full = value.get('repo_name_full')
522 repo_name_full = value.get('repo_name_full')
523 group_path = value.get('group_path')
523 group_path = value.get('group_path')
524 group_name = value.get('group_name')
524 group_name = value.get('group_name')
525
525
526 if repo_name in [ADMIN_PREFIX, '']:
526 if repo_name in [ADMIN_PREFIX, '']:
527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
528 raise formencode.Invalid(
528 raise formencode.Invalid(
529 msg, value, state, error_dict={'repo_name': msg})
529 msg, value, state, error_dict={'repo_name': msg})
530
530
531 rename = old_data.get('repo_name') != repo_name_full
531 rename = old_data.get('repo_name') != repo_name_full
532 create = not edit
532 create = not edit
533 if rename or create:
533 if rename or create:
534
534
535 if group_path:
535 if group_path:
536 if Repository.get_by_repo_name(repo_name_full):
536 if Repository.get_by_repo_name(repo_name_full):
537 msg = M(self, 'repository_in_group_exists', state,
537 msg = M(self, 'repository_in_group_exists', state,
538 repo=repo_name, group=group_name)
538 repo=repo_name, group=group_name)
539 raise formencode.Invalid(
539 raise formencode.Invalid(
540 msg, value, state, error_dict={'repo_name': msg})
540 msg, value, state, error_dict={'repo_name': msg})
541 if RepoGroup.get_by_group_name(repo_name_full):
541 if RepoGroup.get_by_group_name(repo_name_full):
542 msg = M(self, 'group_in_group_exists', state,
542 msg = M(self, 'group_in_group_exists', state,
543 repo=repo_name, group=group_name)
543 repo=repo_name, group=group_name)
544 raise formencode.Invalid(
544 raise formencode.Invalid(
545 msg, value, state, error_dict={'repo_name': msg})
545 msg, value, state, error_dict={'repo_name': msg})
546 else:
546 else:
547 if RepoGroup.get_by_group_name(repo_name_full):
547 if RepoGroup.get_by_group_name(repo_name_full):
548 msg = M(self, 'group_exists', state, repo=repo_name)
548 msg = M(self, 'group_exists', state, repo=repo_name)
549 raise formencode.Invalid(
549 raise formencode.Invalid(
550 msg, value, state, error_dict={'repo_name': msg})
550 msg, value, state, error_dict={'repo_name': msg})
551
551
552 if Repository.get_by_repo_name(repo_name_full):
552 if Repository.get_by_repo_name(repo_name_full):
553 msg = M(
553 msg = M(
554 self, 'repository_exists', state, repo=repo_name)
554 self, 'repository_exists', state, repo=repo_name)
555 raise formencode.Invalid(
555 raise formencode.Invalid(
556 msg, value, state, error_dict={'repo_name': msg})
556 msg, value, state, error_dict={'repo_name': msg})
557 return value
557 return value
558 return _validator
558 return _validator
559
559
560
560
561 def ValidForkName(*args, **kwargs):
561 def ValidForkName(*args, **kwargs):
562 return ValidRepoName(*args, **kwargs)
562 return ValidRepoName(*args, **kwargs)
563
563
564
564
565 def SlugifyName():
565 def SlugifyName():
566 class _validator(formencode.validators.FancyValidator):
566 class _validator(formencode.validators.FancyValidator):
567
567
568 def _to_python(self, value, state):
568 def _to_python(self, value, state):
569 return repo_name_slug(value)
569 return repo_name_slug(value)
570
570
571 def validate_python(self, value, state):
571 def validate_python(self, value, state):
572 pass
572 pass
573
573
574 return _validator
574 return _validator
575
575
576
576
577 def CannotHaveGitSuffix():
577 def CannotHaveGitSuffix():
578 class _validator(formencode.validators.FancyValidator):
578 class _validator(formencode.validators.FancyValidator):
579 messages = {
579 messages = {
580 'has_git_suffix':
580 'has_git_suffix':
581 _(u'Repository name cannot end with .git'),
581 _(u'Repository name cannot end with .git'),
582 }
582 }
583
583
584 def _to_python(self, value, state):
584 def _to_python(self, value, state):
585 return value
585 return value
586
586
587 def validate_python(self, value, state):
587 def validate_python(self, value, state):
588 if value and value.endswith('.git'):
588 if value and value.endswith('.git'):
589 msg = M(
589 msg = M(
590 self, 'has_git_suffix', state)
590 self, 'has_git_suffix', state)
591 raise formencode.Invalid(
591 raise formencode.Invalid(
592 msg, value, state, error_dict={'repo_name': msg})
592 msg, value, state, error_dict={'repo_name': msg})
593
593
594 return _validator
594 return _validator
595
595
596
596
597 def ValidCloneUri():
597 def ValidCloneUri():
598 class InvalidCloneUrl(Exception):
598 class InvalidCloneUrl(Exception):
599 allowed_prefixes = ()
599 allowed_prefixes = ()
600
600
601 def url_handler(repo_type, url):
601 def url_handler(repo_type, url):
602 config = make_db_config(clear_session=False)
602 config = make_db_config(clear_session=False)
603 if repo_type == 'hg':
603 if repo_type == 'hg':
604 allowed_prefixes = ('http', 'svn+http', 'git+http')
604 allowed_prefixes = ('http', 'svn+http', 'git+http')
605
605
606 if 'http' in url[:4]:
606 if 'http' in url[:4]:
607 # initially check if it's at least the proper URL
607 # initially check if it's at least the proper URL
608 # or does it pass basic auth
608 # or does it pass basic auth
609 MercurialRepository.check_url(url, config)
609 MercurialRepository.check_url(url, config)
610 elif 'svn+http' in url[:8]: # svn->hg import
610 elif 'svn+http' in url[:8]: # svn->hg import
611 SubversionRepository.check_url(url, config)
611 SubversionRepository.check_url(url, config)
612 elif 'git+http' in url[:8]: # git->hg import
612 elif 'git+http' in url[:8]: # git->hg import
613 raise NotImplementedError()
613 raise NotImplementedError()
614 else:
614 else:
615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
616 'Allowed url must start with one of %s'
616 'Allowed url must start with one of %s'
617 % (url, ','.join(allowed_prefixes)))
617 % (url, ','.join(allowed_prefixes)))
618 exc.allowed_prefixes = allowed_prefixes
618 exc.allowed_prefixes = allowed_prefixes
619 raise exc
619 raise exc
620
620
621 elif repo_type == 'git':
621 elif repo_type == 'git':
622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
623 if 'http' in url[:4]:
623 if 'http' in url[:4]:
624 # initially check if it's at least the proper URL
624 # initially check if it's at least the proper URL
625 # or does it pass basic auth
625 # or does it pass basic auth
626 GitRepository.check_url(url, config)
626 GitRepository.check_url(url, config)
627 elif 'svn+http' in url[:8]: # svn->git import
627 elif 'svn+http' in url[:8]: # svn->git import
628 raise NotImplementedError()
628 raise NotImplementedError()
629 elif 'hg+http' in url[:8]: # hg->git import
629 elif 'hg+http' in url[:8]: # hg->git import
630 raise NotImplementedError()
630 raise NotImplementedError()
631 else:
631 else:
632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
633 'Allowed url must start with one of %s'
633 'Allowed url must start with one of %s'
634 % (url, ','.join(allowed_prefixes)))
634 % (url, ','.join(allowed_prefixes)))
635 exc.allowed_prefixes = allowed_prefixes
635 exc.allowed_prefixes = allowed_prefixes
636 raise exc
636 raise exc
637
637
638 class _validator(formencode.validators.FancyValidator):
638 class _validator(formencode.validators.FancyValidator):
639 messages = {
639 messages = {
640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
641 'invalid_clone_uri': _(
641 'invalid_clone_uri': _(
642 u'Invalid clone url, provide a valid clone '
642 u'Invalid clone url, provide a valid clone '
643 u'url starting with one of %(allowed_prefixes)s')
643 u'url starting with one of %(allowed_prefixes)s')
644 }
644 }
645
645
646 def validate_python(self, value, state):
646 def validate_python(self, value, state):
647 repo_type = value.get('repo_type')
647 repo_type = value.get('repo_type')
648 url = value.get('clone_uri')
648 url = value.get('clone_uri')
649
649
650 if url:
650 if url:
651 try:
651 try:
652 url_handler(repo_type, url)
652 url_handler(repo_type, url)
653 except InvalidCloneUrl as e:
653 except InvalidCloneUrl as e:
654 log.warning(e)
654 log.warning(e)
655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
656 allowed_prefixes=','.join(e.allowed_prefixes))
656 allowed_prefixes=','.join(e.allowed_prefixes))
657 raise formencode.Invalid(msg, value, state,
657 raise formencode.Invalid(msg, value, state,
658 error_dict={'clone_uri': msg})
658 error_dict={'clone_uri': msg})
659 except Exception:
659 except Exception:
660 log.exception('Url validation failed')
660 log.exception('Url validation failed')
661 msg = M(self, 'clone_uri', rtype=repo_type)
661 msg = M(self, 'clone_uri', rtype=repo_type)
662 raise formencode.Invalid(msg, value, state,
662 raise formencode.Invalid(msg, value, state,
663 error_dict={'clone_uri': msg})
663 error_dict={'clone_uri': msg})
664 return _validator
664 return _validator
665
665
666
666
667 def ValidForkType(old_data={}):
667 def ValidForkType(old_data={}):
668 class _validator(formencode.validators.FancyValidator):
668 class _validator(formencode.validators.FancyValidator):
669 messages = {
669 messages = {
670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
671 }
671 }
672
672
673 def validate_python(self, value, state):
673 def validate_python(self, value, state):
674 if old_data['repo_type'] != value:
674 if old_data['repo_type'] != value:
675 msg = M(self, 'invalid_fork_type', state)
675 msg = M(self, 'invalid_fork_type', state)
676 raise formencode.Invalid(
676 raise formencode.Invalid(
677 msg, value, state, error_dict={'repo_type': msg}
677 msg, value, state, error_dict={'repo_type': msg}
678 )
678 )
679 return _validator
679 return _validator
680
680
681
681
682 def CanWriteGroup(old_data=None):
682 def CanWriteGroup(old_data=None):
683 class _validator(formencode.validators.FancyValidator):
683 class _validator(formencode.validators.FancyValidator):
684 messages = {
684 messages = {
685 'permission_denied': _(
685 'permission_denied': _(
686 u"You do not have the permission "
686 u"You do not have the permission "
687 u"to create repositories in this group."),
687 u"to create repositories in this group."),
688 'permission_denied_root': _(
688 'permission_denied_root': _(
689 u"You do not have the permission to store repositories in "
689 u"You do not have the permission to store repositories in "
690 u"the root location.")
690 u"the root location.")
691 }
691 }
692
692
693 def _to_python(self, value, state):
693 def _to_python(self, value, state):
694 # root location
694 # root location
695 if value in [-1, "-1"]:
695 if value in [-1, "-1"]:
696 return None
696 return None
697 return value
697 return value
698
698
699 def validate_python(self, value, state):
699 def validate_python(self, value, state):
700 gr = RepoGroup.get(value)
700 gr = RepoGroup.get(value)
701 gr_name = gr.group_name if gr else None # None means ROOT location
701 gr_name = gr.group_name if gr else None # None means ROOT location
702 # create repositories with write permission on group is set to true
702 # create repositories with write permission on group is set to true
703 create_on_write = HasPermissionAny(
703 create_on_write = HasPermissionAny(
704 'hg.create.write_on_repogroup.true')()
704 'hg.create.write_on_repogroup.true')()
705 group_admin = HasRepoGroupPermissionAny('group.admin')(
705 group_admin = HasRepoGroupPermissionAny('group.admin')(
706 gr_name, 'can write into group validator')
706 gr_name, 'can write into group validator')
707 group_write = HasRepoGroupPermissionAny('group.write')(
707 group_write = HasRepoGroupPermissionAny('group.write')(
708 gr_name, 'can write into group validator')
708 gr_name, 'can write into group validator')
709 forbidden = not (group_admin or (group_write and create_on_write))
709 forbidden = not (group_admin or (group_write and create_on_write))
710 can_create_repos = HasPermissionAny(
710 can_create_repos = HasPermissionAny(
711 'hg.admin', 'hg.create.repository')
711 'hg.admin', 'hg.create.repository')
712 gid = (old_data['repo_group'].get('group_id')
712 gid = (old_data['repo_group'].get('group_id')
713 if (old_data and 'repo_group' in old_data) else None)
713 if (old_data and 'repo_group' in old_data) else None)
714 value_changed = gid != safe_int(value)
714 value_changed = gid != safe_int(value)
715 new = not old_data
715 new = not old_data
716 # do check if we changed the value, there's a case that someone got
716 # do check if we changed the value, there's a case that someone got
717 # revoked write permissions to a repository, he still created, we
717 # revoked write permissions to a repository, he still created, we
718 # don't need to check permission if he didn't change the value of
718 # don't need to check permission if he didn't change the value of
719 # groups in form box
719 # groups in form box
720 if value_changed or new:
720 if value_changed or new:
721 # parent group need to be existing
721 # parent group need to be existing
722 if gr and forbidden:
722 if gr and forbidden:
723 msg = M(self, 'permission_denied', state)
723 msg = M(self, 'permission_denied', state)
724 raise formencode.Invalid(
724 raise formencode.Invalid(
725 msg, value, state, error_dict={'repo_type': msg}
725 msg, value, state, error_dict={'repo_type': msg}
726 )
726 )
727 # check if we can write to root location !
727 # check if we can write to root location !
728 elif gr is None and not can_create_repos():
728 elif gr is None and not can_create_repos():
729 msg = M(self, 'permission_denied_root', state)
729 msg = M(self, 'permission_denied_root', state)
730 raise formencode.Invalid(
730 raise formencode.Invalid(
731 msg, value, state, error_dict={'repo_type': msg}
731 msg, value, state, error_dict={'repo_type': msg}
732 )
732 )
733
733
734 return _validator
734 return _validator
735
735
736
736
737 def ValidPerms(type_='repo'):
737 def ValidPerms(type_='repo'):
738 if type_ == 'repo_group':
738 if type_ == 'repo_group':
739 EMPTY_PERM = 'group.none'
739 EMPTY_PERM = 'group.none'
740 elif type_ == 'repo':
740 elif type_ == 'repo':
741 EMPTY_PERM = 'repository.none'
741 EMPTY_PERM = 'repository.none'
742 elif type_ == 'user_group':
742 elif type_ == 'user_group':
743 EMPTY_PERM = 'usergroup.none'
743 EMPTY_PERM = 'usergroup.none'
744
744
745 class _validator(formencode.validators.FancyValidator):
745 class _validator(formencode.validators.FancyValidator):
746 messages = {
746 messages = {
747 'perm_new_member_name':
747 'perm_new_member_name':
748 _(u'This username or user group name is not valid')
748 _(u'This username or user group name is not valid')
749 }
749 }
750
750
751 def _to_python(self, value, state):
751 def _to_python(self, value, state):
752 perm_updates = OrderedSet()
752 perm_updates = OrderedSet()
753 perm_additions = OrderedSet()
753 perm_additions = OrderedSet()
754 perm_deletions = OrderedSet()
754 perm_deletions = OrderedSet()
755 # build a list of permission to update/delete and new permission
755 # build a list of permission to update/delete and new permission
756
756
757 # Read the perm_new_member/perm_del_member attributes and group
757 # Read the perm_new_member/perm_del_member attributes and group
758 # them by they IDs
758 # them by they IDs
759 new_perms_group = defaultdict(dict)
759 new_perms_group = defaultdict(dict)
760 del_perms_group = defaultdict(dict)
760 del_perms_group = defaultdict(dict)
761 for k, v in value.copy().iteritems():
761 for k, v in value.copy().iteritems():
762 if k.startswith('perm_del_member'):
762 if k.startswith('perm_del_member'):
763 # delete from org storage so we don't process that later
763 # delete from org storage so we don't process that later
764 del value[k]
764 del value[k]
765 # part is `id`, `type`
765 # part is `id`, `type`
766 _type, part = k.split('perm_del_member_')
766 _type, part = k.split('perm_del_member_')
767 args = part.split('_')
767 args = part.split('_')
768 if len(args) == 2:
768 if len(args) == 2:
769 _key, pos = args
769 _key, pos = args
770 del_perms_group[pos][_key] = v
770 del_perms_group[pos][_key] = v
771 if k.startswith('perm_new_member'):
771 if k.startswith('perm_new_member'):
772 # delete from org storage so we don't process that later
772 # delete from org storage so we don't process that later
773 del value[k]
773 del value[k]
774 # part is `id`, `type`, `perm`
774 # part is `id`, `type`, `perm`
775 _type, part = k.split('perm_new_member_')
775 _type, part = k.split('perm_new_member_')
776 args = part.split('_')
776 args = part.split('_')
777 if len(args) == 2:
777 if len(args) == 2:
778 _key, pos = args
778 _key, pos = args
779 new_perms_group[pos][_key] = v
779 new_perms_group[pos][_key] = v
780
780
781 # store the deletes
781 # store the deletes
782 for k in sorted(del_perms_group.keys()):
782 for k in sorted(del_perms_group.keys()):
783 perm_dict = del_perms_group[k]
783 perm_dict = del_perms_group[k]
784 del_member = perm_dict.get('id')
784 del_member = perm_dict.get('id')
785 del_type = perm_dict.get('type')
785 del_type = perm_dict.get('type')
786 if del_member and del_type:
786 if del_member and del_type:
787 perm_deletions.add(
787 perm_deletions.add(
788 (del_member, None, del_type))
788 (del_member, None, del_type))
789
789
790 # store additions in order of how they were added in web form
790 # store additions in order of how they were added in web form
791 for k in sorted(new_perms_group.keys()):
791 for k in sorted(new_perms_group.keys()):
792 perm_dict = new_perms_group[k]
792 perm_dict = new_perms_group[k]
793 new_member = perm_dict.get('id')
793 new_member = perm_dict.get('id')
794 new_type = perm_dict.get('type')
794 new_type = perm_dict.get('type')
795 new_perm = perm_dict.get('perm')
795 new_perm = perm_dict.get('perm')
796 if new_member and new_perm and new_type:
796 if new_member and new_perm and new_type:
797 perm_additions.add(
797 perm_additions.add(
798 (new_member, new_perm, new_type))
798 (new_member, new_perm, new_type))
799
799
800 # get updates of permissions
800 # get updates of permissions
801 # (read the existing radio button states)
801 # (read the existing radio button states)
802 default_user_id = User.get_default_user().user_id
802 default_user_id = User.get_default_user().user_id
803 for k, update_value in value.iteritems():
803 for k, update_value in value.iteritems():
804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
805 member = k[7:]
805 member = k[7:]
806 update_type = {'u': 'user',
806 update_type = {'u': 'user',
807 'g': 'users_group'}[k[0]]
807 'g': 'users_group'}[k[0]]
808
808
809 if safe_int(member) == default_user_id:
809 if safe_int(member) == default_user_id:
810 if str2bool(value.get('repo_private')):
810 if str2bool(value.get('repo_private')):
811 # prevent from updating default user permissions
811 # prevent from updating default user permissions
812 # when this repository is marked as private
812 # when this repository is marked as private
813 update_value = EMPTY_PERM
813 update_value = EMPTY_PERM
814
814
815 perm_updates.add(
815 perm_updates.add(
816 (member, update_value, update_type))
816 (member, update_value, update_type))
817
817
818 value['perm_additions'] = [] # propagated later
818 value['perm_additions'] = [] # propagated later
819 value['perm_updates'] = list(perm_updates)
819 value['perm_updates'] = list(perm_updates)
820 value['perm_deletions'] = list(perm_deletions)
820 value['perm_deletions'] = list(perm_deletions)
821
821
822 updates_map = dict(
822 updates_map = dict(
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 # make sure Additions don't override updates.
824 # make sure Additions don't override updates.
825 for member_id, perm, member_type in list(perm_additions):
825 for member_id, perm, member_type in list(perm_additions):
826 if member_id in updates_map:
826 if member_id in updates_map:
827 perm = updates_map[member_id][0]
827 perm = updates_map[member_id][0]
828 value['perm_additions'].append((member_id, perm, member_type))
828 value['perm_additions'].append((member_id, perm, member_type))
829
829
830 # on new entries validate users they exist and they are active !
830 # on new entries validate users they exist and they are active !
831 # this leaves feedback to the form
831 # this leaves feedback to the form
832 try:
832 try:
833 if member_type == 'user':
833 if member_type == 'user':
834 User.query()\
834 User.query()\
835 .filter(User.active == true())\
835 .filter(User.active == true())\
836 .filter(User.user_id == member_id).one()
836 .filter(User.user_id == member_id).one()
837 if member_type == 'users_group':
837 if member_type == 'users_group':
838 UserGroup.query()\
838 UserGroup.query()\
839 .filter(UserGroup.users_group_active == true())\
839 .filter(UserGroup.users_group_active == true())\
840 .filter(UserGroup.users_group_id == member_id)\
840 .filter(UserGroup.users_group_id == member_id)\
841 .one()
841 .one()
842
842
843 except Exception:
843 except Exception:
844 log.exception('Updated permission failed: org_exc:')
844 log.exception('Updated permission failed: org_exc:')
845 msg = M(self, 'perm_new_member_type', state)
845 msg = M(self, 'perm_new_member_type', state)
846 raise formencode.Invalid(
846 raise formencode.Invalid(
847 msg, value, state, error_dict={
847 msg, value, state, error_dict={
848 'perm_new_member_name': msg}
848 'perm_new_member_name': msg}
849 )
849 )
850 return value
850 return value
851 return _validator
851 return _validator
852
852
853
853
854 def ValidSettings():
854 def ValidSettings():
855 class _validator(formencode.validators.FancyValidator):
855 class _validator(formencode.validators.FancyValidator):
856 def _to_python(self, value, state):
856 def _to_python(self, value, state):
857 # settings form for users that are not admin
857 # settings form for users that are not admin
858 # can't edit certain parameters, it's extra backup if they mangle
858 # can't edit certain parameters, it's extra backup if they mangle
859 # with forms
859 # with forms
860
860
861 forbidden_params = [
861 forbidden_params = [
862 'user', 'repo_type', 'repo_enable_locking',
862 'user', 'repo_type', 'repo_enable_locking',
863 'repo_enable_downloads', 'repo_enable_statistics'
863 'repo_enable_downloads', 'repo_enable_statistics'
864 ]
864 ]
865
865
866 for param in forbidden_params:
866 for param in forbidden_params:
867 if param in value:
867 if param in value:
868 del value[param]
868 del value[param]
869 return value
869 return value
870
870
871 def validate_python(self, value, state):
871 def validate_python(self, value, state):
872 pass
872 pass
873 return _validator
873 return _validator
874
874
875
875
876 def ValidPath():
876 def ValidPath():
877 class _validator(formencode.validators.FancyValidator):
877 class _validator(formencode.validators.FancyValidator):
878 messages = {
878 messages = {
879 'invalid_path': _(u'This is not a valid path')
879 'invalid_path': _(u'This is not a valid path')
880 }
880 }
881
881
882 def validate_python(self, value, state):
882 def validate_python(self, value, state):
883 if not os.path.isdir(value):
883 if not os.path.isdir(value):
884 msg = M(self, 'invalid_path', state)
884 msg = M(self, 'invalid_path', state)
885 raise formencode.Invalid(
885 raise formencode.Invalid(
886 msg, value, state, error_dict={'paths_root_path': msg}
886 msg, value, state, error_dict={'paths_root_path': msg}
887 )
887 )
888 return _validator
888 return _validator
889
889
890
890
891 def UniqSystemEmail(old_data={}):
891 def UniqSystemEmail(old_data={}):
892 class _validator(formencode.validators.FancyValidator):
892 class _validator(formencode.validators.FancyValidator):
893 messages = {
893 messages = {
894 'email_taken': _(u'This e-mail address is already taken')
894 'email_taken': _(u'This e-mail address is already taken')
895 }
895 }
896
896
897 def _to_python(self, value, state):
897 def _to_python(self, value, state):
898 return value.lower()
898 return value.lower()
899
899
900 def validate_python(self, value, state):
900 def validate_python(self, value, state):
901 if (old_data.get('email') or '').lower() != value:
901 if (old_data.get('email') or '').lower() != value:
902 user = User.get_by_email(value, case_insensitive=True)
902 user = User.get_by_email(value, case_insensitive=True)
903 if user:
903 if user:
904 msg = M(self, 'email_taken', state)
904 msg = M(self, 'email_taken', state)
905 raise formencode.Invalid(
905 raise formencode.Invalid(
906 msg, value, state, error_dict={'email': msg}
906 msg, value, state, error_dict={'email': msg}
907 )
907 )
908 return _validator
908 return _validator
909
909
910
910
911 def ValidSystemEmail():
911 def ValidSystemEmail():
912 class _validator(formencode.validators.FancyValidator):
912 class _validator(formencode.validators.FancyValidator):
913 messages = {
913 messages = {
914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
915 }
915 }
916
916
917 def _to_python(self, value, state):
917 def _to_python(self, value, state):
918 return value.lower()
918 return value.lower()
919
919
920 def validate_python(self, value, state):
920 def validate_python(self, value, state):
921 user = User.get_by_email(value, case_insensitive=True)
921 user = User.get_by_email(value, case_insensitive=True)
922 if user is None:
922 if user is None:
923 msg = M(self, 'non_existing_email', state, email=value)
923 msg = M(self, 'non_existing_email', state, email=value)
924 raise formencode.Invalid(
924 raise formencode.Invalid(
925 msg, value, state, error_dict={'email': msg}
925 msg, value, state, error_dict={'email': msg}
926 )
926 )
927
927
928 return _validator
928 return _validator
929
929
930
930
931 def NotReviewedRevisions(repo_id):
931 def NotReviewedRevisions(repo_id):
932 class _validator(formencode.validators.FancyValidator):
932 class _validator(formencode.validators.FancyValidator):
933 messages = {
933 messages = {
934 'rev_already_reviewed':
934 'rev_already_reviewed':
935 _(u'Revisions %(revs)s are already part of pull request '
935 _(u'Revisions %(revs)s are already part of pull request '
936 u'or have set status'),
936 u'or have set status'),
937 }
937 }
938
938
939 def validate_python(self, value, state):
939 def validate_python(self, value, state):
940 # check revisions if they are not reviewed, or a part of another
940 # check revisions if they are not reviewed, or a part of another
941 # pull request
941 # pull request
942 statuses = ChangesetStatus.query()\
942 statuses = ChangesetStatus.query()\
943 .filter(ChangesetStatus.revision.in_(value))\
943 .filter(ChangesetStatus.revision.in_(value))\
944 .filter(ChangesetStatus.repo_id == repo_id)\
944 .filter(ChangesetStatus.repo_id == repo_id)\
945 .all()
945 .all()
946
946
947 errors = []
947 errors = []
948 for status in statuses:
948 for status in statuses:
949 if status.pull_request_id:
949 if status.pull_request_id:
950 errors.append(['pull_req', status.revision[:12]])
950 errors.append(['pull_req', status.revision[:12]])
951 elif status.status:
951 elif status.status:
952 errors.append(['status', status.revision[:12]])
952 errors.append(['status', status.revision[:12]])
953
953
954 if errors:
954 if errors:
955 revs = ','.join([x[1] for x in errors])
955 revs = ','.join([x[1] for x in errors])
956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
957 raise formencode.Invalid(
957 raise formencode.Invalid(
958 msg, value, state, error_dict={'revisions': revs})
958 msg, value, state, error_dict={'revisions': revs})
959
959
960 return _validator
960 return _validator
961
961
962
962
963 def ValidIp():
963 def ValidIp():
964 class _validator(CIDR):
964 class _validator(CIDR):
965 messages = {
965 messages = {
966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
967 'illegalBits': _(
967 'illegalBits': _(
968 u'The network size (bits) must be within the range '
968 u'The network size (bits) must be within the range '
969 u'of 0-32 (not %(bits)r)'),
969 u'of 0-32 (not %(bits)r)'),
970 }
970 }
971
971
972 # we ovveride the default to_python() call
972 # we ovveride the default to_python() call
973 def to_python(self, value, state):
973 def to_python(self, value, state):
974 v = super(_validator, self).to_python(value, state)
974 v = super(_validator, self).to_python(value, state)
975 v = safe_unicode(v.strip())
975 v = safe_unicode(v.strip())
976 net = ipaddress.ip_network(address=v, strict=False)
976 net = ipaddress.ip_network(address=v, strict=False)
977 return str(net)
977 return str(net)
978
978
979 def validate_python(self, value, state):
979 def validate_python(self, value, state):
980 try:
980 try:
981 addr = safe_unicode(value.strip())
981 addr = safe_unicode(value.strip())
982 # this raises an ValueError if address is not IpV4 or IpV6
982 # this raises an ValueError if address is not IpV4 or IpV6
983 ipaddress.ip_network(addr, strict=False)
983 ipaddress.ip_network(addr, strict=False)
984 except ValueError:
984 except ValueError:
985 raise formencode.Invalid(self.message('badFormat', state),
985 raise formencode.Invalid(self.message('badFormat', state),
986 value, state)
986 value, state)
987
987
988 return _validator
988 return _validator
989
989
990
990
991 def FieldKey():
991 def FieldKey():
992 class _validator(formencode.validators.FancyValidator):
992 class _validator(formencode.validators.FancyValidator):
993 messages = {
993 messages = {
994 'badFormat': _(
994 'badFormat': _(
995 u'Key name can only consist of letters, '
995 u'Key name can only consist of letters, '
996 u'underscore, dash or numbers'),
996 u'underscore, dash or numbers'),
997 }
997 }
998
998
999 def validate_python(self, value, state):
999 def validate_python(self, value, state):
1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1001 raise formencode.Invalid(self.message('badFormat', state),
1001 raise formencode.Invalid(self.message('badFormat', state),
1002 value, state)
1002 value, state)
1003 return _validator
1003 return _validator
1004
1004
1005
1005
1006 def ValidAuthPlugins():
1006 def ValidAuthPlugins():
1007 class _validator(formencode.validators.FancyValidator):
1007 class _validator(formencode.validators.FancyValidator):
1008 messages = {
1008 messages = {
1009 'import_duplicate': _(
1009 'import_duplicate': _(
1010 u'Plugins %(loaded)s and %(next_to_load)s '
1010 u'Plugins %(loaded)s and %(next_to_load)s '
1011 u'both export the same name'),
1011 u'both export the same name'),
1012 'missing_includeme': _(
1012 'missing_includeme': _(
1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1014 u'function.'),
1014 u'function.'),
1015 'import_error': _(
1015 'import_error': _(
1016 u'Can not load plugin "%(plugin_id)s"'),
1016 u'Can not load plugin "%(plugin_id)s"'),
1017 'no_plugin': _(
1017 'no_plugin': _(
1018 u'No plugin available with ID "%(plugin_id)s"'),
1018 u'No plugin available with ID "%(plugin_id)s"'),
1019 }
1019 }
1020
1020
1021 def _to_python(self, value, state):
1021 def _to_python(self, value, state):
1022 # filter empty values
1022 # filter empty values
1023 return filter(lambda s: s not in [None, ''], value)
1023 return filter(lambda s: s not in [None, ''], value)
1024
1024
1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1026 """
1026 """
1027 Validates that the plugin import works. It also checks that the
1027 Validates that the plugin import works. It also checks that the
1028 plugin has an includeme attribute.
1028 plugin has an includeme attribute.
1029 """
1029 """
1030 try:
1030 try:
1031 plugin = _import_legacy_plugin(plugin_id)
1031 plugin = _import_legacy_plugin(plugin_id)
1032 except Exception as e:
1032 except Exception as e:
1033 log.exception(
1033 log.exception(
1034 'Exception during import of auth legacy plugin "{}"'
1034 'Exception during import of auth legacy plugin "{}"'
1035 .format(plugin_id))
1035 .format(plugin_id))
1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1037 raise formencode.Invalid(msg, value, state)
1037 raise formencode.Invalid(msg, value, state)
1038
1038
1039 if not hasattr(plugin, 'includeme'):
1039 if not hasattr(plugin, 'includeme'):
1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1041 raise formencode.Invalid(msg, value, state)
1041 raise formencode.Invalid(msg, value, state)
1042
1042
1043 return plugin
1043 return plugin
1044
1044
1045 def _validate_plugin_id(self, plugin_id, value, state):
1045 def _validate_plugin_id(self, plugin_id, value, state):
1046 """
1046 """
1047 Plugins are already imported during app start up. Therefore this
1047 Plugins are already imported during app start up. Therefore this
1048 validation only retrieves the plugin from the plugin registry and
1048 validation only retrieves the plugin from the plugin registry and
1049 if it returns something not None everything is OK.
1049 if it returns something not None everything is OK.
1050 """
1050 """
1051 plugin = loadplugin(plugin_id)
1051 plugin = loadplugin(plugin_id)
1052
1052
1053 if plugin is None:
1053 if plugin is None:
1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1055 raise formencode.Invalid(msg, value, state)
1055 raise formencode.Invalid(msg, value, state)
1056
1056
1057 return plugin
1057 return plugin
1058
1058
1059 def validate_python(self, value, state):
1059 def validate_python(self, value, state):
1060 unique_names = {}
1060 unique_names = {}
1061 for plugin_id in value:
1061 for plugin_id in value:
1062
1062
1063 # Validate legacy or normal plugin.
1063 # Validate legacy or normal plugin.
1064 if plugin_id.startswith(legacy_plugin_prefix):
1064 if plugin_id.startswith(legacy_plugin_prefix):
1065 plugin = self._validate_legacy_plugin_id(
1065 plugin = self._validate_legacy_plugin_id(
1066 plugin_id, value, state)
1066 plugin_id, value, state)
1067 else:
1067 else:
1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1069
1069
1070 # Only allow unique plugin names.
1070 # Only allow unique plugin names.
1071 if plugin.name in unique_names:
1071 if plugin.name in unique_names:
1072 msg = M(self, 'import_duplicate', state,
1072 msg = M(self, 'import_duplicate', state,
1073 loaded=unique_names[plugin.name],
1073 loaded=unique_names[plugin.name],
1074 next_to_load=plugin)
1074 next_to_load=plugin)
1075 raise formencode.Invalid(msg, value, state)
1075 raise formencode.Invalid(msg, value, state)
1076 unique_names[plugin.name] = plugin
1076 unique_names[plugin.name] = plugin
1077
1077
1078 return _validator
1078 return _validator
1079
1079
1080
1080
1081 def ValidPattern():
1081 def ValidPattern():
1082
1082
1083 class _Validator(formencode.validators.FancyValidator):
1083 class _Validator(formencode.validators.FancyValidator):
1084
1084
1085 def _to_python(self, value, state):
1085 def _to_python(self, value, state):
1086 patterns = []
1086 patterns = []
1087
1087
1088 prefix = 'new_pattern'
1088 prefix = 'new_pattern'
1089 for name, v in value.iteritems():
1089 for name, v in value.iteritems():
1090 pattern_name = '_'.join((prefix, 'pattern'))
1090 pattern_name = '_'.join((prefix, 'pattern'))
1091 if name.startswith(pattern_name):
1091 if name.startswith(pattern_name):
1092 new_item_id = name[len(pattern_name)+1:]
1092 new_item_id = name[len(pattern_name)+1:]
1093
1093
1094 def _field(name):
1094 def _field(name):
1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1096
1096
1097 values = {
1097 values = {
1098 'issuetracker_pat': value.get(_field('pattern')),
1098 'issuetracker_pat': value.get(_field('pattern')),
1099 'issuetracker_pat': value.get(_field('pattern')),
1099 'issuetracker_pat': value.get(_field('pattern')),
1100 'issuetracker_url': value.get(_field('url')),
1100 'issuetracker_url': value.get(_field('url')),
1101 'issuetracker_pref': value.get(_field('prefix')),
1101 'issuetracker_pref': value.get(_field('prefix')),
1102 'issuetracker_desc': value.get(_field('description'))
1102 'issuetracker_desc': value.get(_field('description'))
1103 }
1103 }
1104 new_uid = md5(values['issuetracker_pat'])
1104 new_uid = md5(values['issuetracker_pat'])
1105
1105
1106 has_required_fields = (
1106 has_required_fields = (
1107 values['issuetracker_pat']
1107 values['issuetracker_pat']
1108 and values['issuetracker_url'])
1108 and values['issuetracker_url'])
1109
1109
1110 if has_required_fields:
1110 if has_required_fields:
1111 settings = [
1111 settings = [
1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1113 for key in values]
1113 for key in values]
1114 patterns.append(settings)
1114 patterns.append(settings)
1115
1115
1116 value['patterns'] = patterns
1116 value['patterns'] = patterns
1117 delete_patterns = value.get('uid') or []
1117 delete_patterns = value.get('uid') or []
1118 if not isinstance(delete_patterns, (list, tuple)):
1118 if not isinstance(delete_patterns, (list, tuple)):
1119 delete_patterns = [delete_patterns]
1119 delete_patterns = [delete_patterns]
1120 value['delete_patterns'] = delete_patterns
1120 value['delete_patterns'] = delete_patterns
1121 return value
1121 return value
1122 return _Validator
1122 return _Validator
@@ -1,258 +1,269 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('favicon', '/favicon.ico', []);
17 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('robots', '/robots.txt', []);
18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('admin_home', '/_admin', []);
37 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
62 pyroutes.register('users', '/_admin/users', []);
62 pyroutes.register('users', '/_admin/users', []);
63 pyroutes.register('users_data', '/_admin/users_data', []);
63 pyroutes.register('users_data', '/_admin/users_data', []);
64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
66 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
66 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
68 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
68 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
69 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
69 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
70 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
70 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
71 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
71 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
72 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
72 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
73 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
73 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
74 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
74 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
75 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
75 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
76 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
76 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
77 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
77 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
78 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
78 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
82 pyroutes.register('user_groups', '/_admin/user_groups', []);
82 pyroutes.register('user_groups', '/_admin/user_groups', []);
83 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
83 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
84 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
84 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
85 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
85 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
86 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
87 pyroutes.register('repos', '/_admin/repos', []);
86 pyroutes.register('repos', '/_admin/repos', []);
88 pyroutes.register('repo_new', '/_admin/repos/new', []);
87 pyroutes.register('repo_new', '/_admin/repos/new', []);
89 pyroutes.register('repo_create', '/_admin/repos/create', []);
88 pyroutes.register('repo_create', '/_admin/repos/create', []);
90 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
91 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
90 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
92 pyroutes.register('channelstream_proxy', '/_channelstream', []);
91 pyroutes.register('channelstream_proxy', '/_channelstream', []);
93 pyroutes.register('login', '/_admin/login', []);
92 pyroutes.register('login', '/_admin/login', []);
94 pyroutes.register('logout', '/_admin/logout', []);
93 pyroutes.register('logout', '/_admin/logout', []);
95 pyroutes.register('register', '/_admin/register', []);
94 pyroutes.register('register', '/_admin/register', []);
96 pyroutes.register('reset_password', '/_admin/password_reset', []);
95 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
96 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 pyroutes.register('home', '/', []);
97 pyroutes.register('home', '/', []);
99 pyroutes.register('user_autocomplete_data', '/_users', []);
98 pyroutes.register('user_autocomplete_data', '/_users', []);
100 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
99 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
101 pyroutes.register('repo_list_data', '/_repos', []);
100 pyroutes.register('repo_list_data', '/_repos', []);
102 pyroutes.register('goto_switcher_data', '/_goto_data', []);
101 pyroutes.register('goto_switcher_data', '/_goto_data', []);
103 pyroutes.register('journal', '/_admin/journal', []);
102 pyroutes.register('journal', '/_admin/journal', []);
104 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
103 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
105 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
104 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
106 pyroutes.register('journal_public', '/_admin/public_journal', []);
105 pyroutes.register('journal_public', '/_admin/public_journal', []);
107 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
106 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
108 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
107 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
109 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
108 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
110 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
109 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
111 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
110 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
112 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
111 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
113 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
112 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
114 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
113 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
115 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
114 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
116 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
124 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
125 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
124 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
126 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
125 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
127 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
126 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
128 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
127 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
129 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
128 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
130 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
130 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
132 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
131 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
133 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
136 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
149 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
151 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
150 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
152 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
151 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
153 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
152 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
154 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
153 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
154 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
156 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
155 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
157 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
156 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
158 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
157 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
159 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
158 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
160 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
159 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
161 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
160 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
162 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
161 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
163 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
162 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
164 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
163 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
165 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
164 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
166 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
165 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
167 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
166 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
168 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
167 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
169 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
168 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
170 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
169 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
171 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
170 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
172 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
171 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
173 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
172 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
174 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
173 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
175 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
174 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
176 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
175 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
177 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
176 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
178 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
177 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
179 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
178 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
180 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
179 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
181 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
180 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
182 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
181 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
183 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
182 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
184 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
183 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
185 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
184 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
186 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
185 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
187 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
186 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
188 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
187 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
189 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
188 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
190 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
189 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
191 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
190 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
192 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
191 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
193 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
192 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
194 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
193 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
195 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
194 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
196 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
195 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
197 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
196 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
198 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
197 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
199 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
198 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
200 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
199 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
201 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
200 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
202 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
201 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
203 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
202 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
204 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
203 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
205 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
204 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
206 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
205 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
207 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
206 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
208 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
207 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
209 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
208 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
210 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
209 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
211 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
210 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
212 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
211 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
212 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
213 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
214 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
215 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
216 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
217 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
218 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
219 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
220 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
221 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
222 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
223 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
213 pyroutes.register('search', '/_admin/search', []);
224 pyroutes.register('search', '/_admin/search', []);
214 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
225 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
215 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
226 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
216 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
227 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
228 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
229 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
219 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
230 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
220 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
231 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
221 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
232 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
222 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
233 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
223 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
234 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
224 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
235 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
225 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
236 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
226 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
237 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
227 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
238 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
228 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
239 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
229 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
240 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
230 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
241 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
231 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
242 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
232 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
243 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
233 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
244 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
234 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
245 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
235 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
246 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
236 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
247 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
237 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
248 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
238 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
249 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
239 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
250 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
240 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
251 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
241 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
252 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
242 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
253 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
243 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
254 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
244 pyroutes.register('gists_show', '/_admin/gists', []);
255 pyroutes.register('gists_show', '/_admin/gists', []);
245 pyroutes.register('gists_new', '/_admin/gists/new', []);
256 pyroutes.register('gists_new', '/_admin/gists/new', []);
246 pyroutes.register('gists_create', '/_admin/gists/create', []);
257 pyroutes.register('gists_create', '/_admin/gists/create', []);
247 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
258 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
248 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
259 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
249 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
260 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
250 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
261 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
251 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
262 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
252 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
263 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
253 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
264 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
254 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
265 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
255 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
266 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
256 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
267 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
257 pyroutes.register('apiv2', '/_admin/api', []);
268 pyroutes.register('apiv2', '/_admin/api', []);
258 }
269 }
@@ -1,148 +1,148 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')}
8 ${h.secure_form(h.url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-user">${_('User/User Group')}</th>
15 <th class="td-user">${_('User/User Group')}</th>
16 <th></th>
16 <th></th>
17 </tr>
17 </tr>
18 ## USERS
18 ## USERS
19 %for _user in c.repo_group.permissions():
19 %for _user in c.repo_group.permissions():
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 <tr class="perm_admin_row">
21 <tr class="perm_admin_row">
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-user">
26 <td class="td-user">
27 ${base.gravatar(_user.email, 16)}
27 ${base.gravatar(_user.email, 16)}
28 <span class="user">
28 <span class="user">
29 ${h.link_to_user(_user.username)}
29 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
30 %if getattr(_user, 'admin_row', None):
31 (${_('super admin')})
31 (${_('super admin')})
32 %endif
32 %endif
33 %if getattr(_user, 'owner_row', None):
33 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
34 (${_('owner')})
35 %endif
35 %endif
36 </span>
36 </span>
37 </td>
37 </td>
38 <td></td>
38 <td></td>
39 </tr>
39 </tr>
40 %else:
40 %else:
41 ##forbid revoking permission from yourself, except if you're an super admin
41 ##forbid revoking permission from yourself, except if you're an super admin
42 <tr>
42 <tr>
43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none')}</td>
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
48 <td class="td-user">
48 <td class="td-user">
49 ${base.gravatar(_user.email, 16)}
49 ${base.gravatar(_user.email, 16)}
50 <span class="user">
50 <span class="user">
51 % if _user.username == h.DEFAULT_USER:
51 % if _user.username == h.DEFAULT_USER:
52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 % else:
53 % else:
54 ${h.link_to_user(_user.username)}
54 ${h.link_to_user(_user.username)}
55 % endif
55 % endif
56 </span>
56 </span>
57 </td>
57 </td>
58 <td class="td-action">
58 <td class="td-action">
59 %if _user.username != h.DEFAULT_USER:
59 %if _user.username != h.DEFAULT_USER:
60 <span class="btn btn-link btn-danger revoke_perm"
60 <span class="btn btn-link btn-danger revoke_perm"
61 member="${_user.user_id}" member_type="user">
61 member="${_user.user_id}" member_type="user">
62 <i class="icon-remove"></i> ${_('Revoke')}
62 <i class="icon-remove"></i> ${_('Revoke')}
63 </span>
63 </span>
64 %endif
64 %endif
65 </td>
65 </td>
66 %else:
66 %else:
67 ## special case for current user permissions, we make sure he cannot take his own permissions
67 ## special case for current user permissions, we make sure he cannot take his own permissions
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
72 <td class="td-user">
72 <td class="td-user">
73 ${base.gravatar(_user.email, 16)}
73 ${base.gravatar(_user.email, 16)}
74 <span class="user">
74 <span class="user">
75 % if _user.username == h.DEFAULT_USER:
75 % if _user.username == h.DEFAULT_USER:
76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 % else:
77 % else:
78 ${h.link_to_user(_user.username)}
78 ${h.link_to_user(_user.username)}
79 % endif
79 % endif
80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 </span>
81 </span>
82 </td>
82 </td>
83 <td></td>
83 <td></td>
84 %endif
84 %endif
85 </tr>
85 </tr>
86 %endif
86 %endif
87 %endfor
87 %endfor
88
88
89 ## USER GROUPS
89 ## USER GROUPS
90 %for _user_group in c.repo_group.permission_user_groups():
90 %for _user_group in c.repo_group.permission_user_groups():
91 <tr id="id${id(_user_group.users_group_name)}">
91 <tr id="id${id(_user_group.users_group_name)}">
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}</td>
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</td>
96 <td class="td-componentname">
96 <td class="td-componentname">
97 <i class="icon-group" ></i>
97 <i class="icon-group" ></i>
98 %if h.HasPermissionAny('hg.admin')():
98 %if h.HasPermissionAny('hg.admin')():
99 <a href="${h.url('edit_users_group',user_group_id=_user_group.users_group_id)}">
99 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
100 ${_user_group.users_group_name}
100 ${_user_group.users_group_name}
101 </a>
101 </a>
102 %else:
102 %else:
103 ${_user_group.users_group_name}
103 ${_user_group.users_group_name}
104 %endif
104 %endif
105 </td>
105 </td>
106 <td class="td-action">
106 <td class="td-action">
107 <span class="btn btn-link btn-danger revoke_perm"
107 <span class="btn btn-link btn-danger revoke_perm"
108 member="${_user_group.users_group_id}" member_type="user_group">
108 member="${_user_group.users_group_id}" member_type="user_group">
109 <i class="icon-remove"></i> ${_('Revoke')}
109 <i class="icon-remove"></i> ${_('Revoke')}
110 </span>
110 </span>
111 </td>
111 </td>
112 </tr>
112 </tr>
113 %endfor
113 %endfor
114
114
115 <tr class="new_members" id="add_perm_input"></tr>
115 <tr class="new_members" id="add_perm_input"></tr>
116 </table>
116 </table>
117 <div id="add_perm" class="link">
117 <div id="add_perm" class="link">
118 ${_('Add new')}
118 ${_('Add new')}
119 </div>
119 </div>
120 <div class="fields">
120 <div class="fields">
121 <div class="field">
121 <div class="field">
122 <div class="label label-radio">
122 <div class="label label-radio">
123 ${_('Apply to children')}:
123 ${_('Apply to children')}:
124 </div>
124 </div>
125 <div class="radios">
125 <div class="radios">
126 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
126 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
127 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
127 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
128 ${h.radio('recursive', 'repos', label=_('Repositories'))}
128 ${h.radio('recursive', 'repos', label=_('Repositories'))}
129 ${h.radio('recursive', 'all', label=_('Both'))}
129 ${h.radio('recursive', 'all', label=_('Both'))}
130 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
130 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134 <div class="buttons">
134 <div class="buttons">
135 ${h.submit('save',_('Save'),class_="btn btn-primary")}
135 ${h.submit('save',_('Save'),class_="btn btn-primary")}
136 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
136 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
137 </div>
137 </div>
138 ${h.end_form()}
138 ${h.end_form()}
139 </div>
139 </div>
140 </div>
140 </div>
141 <script type="text/javascript">
141 <script type="text/javascript">
142 $('#add_perm').on('click', function(e){
142 $('#add_perm').on('click', function(e){
143 addNewPermInput($(this), 'group');
143 addNewPermInput($(this), 'group');
144 });
144 });
145 $('.revoke_perm').on('click', function(e){
145 $('.revoke_perm').on('click', function(e){
146 markRevokePermInput($(this), 'group');
146 markRevokePermInput($(this), 'group');
147 })
147 })
148 </script>
148 </script>
@@ -1,123 +1,123 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th></th>
16 <th></th>
17 </tr>
17 </tr>
18 ## USERS
18 ## USERS
19 %for _user in c.repo_info.permissions():
19 %for _user in c.repo_info.permissions():
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 <tr class="perm_admin_row">
21 <tr class="perm_admin_row">
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-user">
26 <td class="td-user">
27 ${base.gravatar(_user.email, 16)}
27 ${base.gravatar(_user.email, 16)}
28 ${h.link_to_user(_user.username)}
28 ${h.link_to_user(_user.username)}
29 %if getattr(_user, 'admin_row', None):
29 %if getattr(_user, 'admin_row', None):
30 (${_('super admin')})
30 (${_('super admin')})
31 %endif
31 %endif
32 %if getattr(_user, 'owner_row', None):
32 %if getattr(_user, 'owner_row', None):
33 (${_('owner')})
33 (${_('owner')})
34 %endif
34 %endif
35 </td>
35 </td>
36 <td></td>
36 <td></td>
37 </tr>
37 </tr>
38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
39 <tr>
39 <tr>
40 <td colspan="4">
40 <td colspan="4">
41 <span class="private_repo_msg">
41 <span class="private_repo_msg">
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
43 </span>
43 </span>
44 </td>
44 </td>
45 <td class="private_repo_msg">
45 <td class="private_repo_msg">
46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
48 <td></td>
48 <td></td>
49 </tr>
49 </tr>
50 %else:
50 %else:
51 <tr>
51 <tr>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
56 <td class="td-user">
56 <td class="td-user">
57 ${base.gravatar(_user.email, 16)}
57 ${base.gravatar(_user.email, 16)}
58 <span class="user">
58 <span class="user">
59 % if _user.username == h.DEFAULT_USER:
59 % if _user.username == h.DEFAULT_USER:
60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
61 % else:
61 % else:
62 ${h.link_to_user(_user.username)}
62 ${h.link_to_user(_user.username)}
63 % endif
63 % endif
64 </span>
64 </span>
65 </td>
65 </td>
66 <td class="td-action">
66 <td class="td-action">
67 %if _user.username != h.DEFAULT_USER:
67 %if _user.username != h.DEFAULT_USER:
68 <span class="btn btn-link btn-danger revoke_perm"
68 <span class="btn btn-link btn-danger revoke_perm"
69 member="${_user.user_id}" member_type="user">
69 member="${_user.user_id}" member_type="user">
70 <i class="icon-remove"></i> ${_('Revoke')}
70 <i class="icon-remove"></i> ${_('Revoke')}
71 </span>
71 </span>
72 %endif
72 %endif
73 </td>
73 </td>
74 </tr>
74 </tr>
75 %endif
75 %endif
76 %endfor
76 %endfor
77
77
78 ## USER GROUPS
78 ## USER GROUPS
79 %for _user_group in c.repo_info.permission_user_groups():
79 %for _user_group in c.repo_info.permission_user_groups():
80 <tr>
80 <tr>
81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
85 <td class="td-componentname">
85 <td class="td-componentname">
86 <i class="icon-group" ></i>
86 <i class="icon-group" ></i>
87 %if h.HasPermissionAny('hg.admin')():
87 %if h.HasPermissionAny('hg.admin')():
88 <a href="${h.url('edit_users_group',user_group_id=_user_group.users_group_id)}">
88 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
89 ${_user_group.users_group_name}
89 ${_user_group.users_group_name}
90 </a>
90 </a>
91 %else:
91 %else:
92 ${_user_group.users_group_name}
92 ${_user_group.users_group_name}
93 %endif
93 %endif
94 </td>
94 </td>
95 <td class="td-action">
95 <td class="td-action">
96 <span class="btn btn-link btn-danger revoke_perm"
96 <span class="btn btn-link btn-danger revoke_perm"
97 member="${_user_group.users_group_id}" member_type="user_group">
97 member="${_user_group.users_group_id}" member_type="user_group">
98 <i class="icon-remove"></i> ${_('Revoke')}
98 <i class="icon-remove"></i> ${_('Revoke')}
99 </span>
99 </span>
100 </td>
100 </td>
101 </tr>
101 </tr>
102 %endfor
102 %endfor
103 <tr class="new_members" id="add_perm_input"></tr>
103 <tr class="new_members" id="add_perm_input"></tr>
104 </table>
104 </table>
105 <div id="add_perm" class="link">
105 <div id="add_perm" class="link">
106 ${_('Add new')}
106 ${_('Add new')}
107 </div>
107 </div>
108 <div class="buttons">
108 <div class="buttons">
109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 </div>
113 </div>
114 </div>
114 </div>
115
115
116 <script type="text/javascript">
116 <script type="text/javascript">
117 $('#add_perm').on('click', function(e){
117 $('#add_perm').on('click', function(e){
118 addNewPermInput($(this), 'repository');
118 addNewPermInput($(this), 'repository');
119 });
119 });
120 $('.revoke_perm').on('click', function(e){
120 $('.revoke_perm').on('click', function(e){
121 markRevokePermInput($(this), 'repository');
121 markRevokePermInput($(this), 'repository');
122 });
122 });
123 </script>
123 </script>
@@ -1,72 +1,72 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add user group')}
5 ${_('Add user group')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('User groups'),h.url('users_groups'))}
13 ${h.link_to(_('User groups'),h.route_path('user_groups'))}
14 &raquo;
14 &raquo;
15 ${_('Add User Group')}
15 ${_('Add User Group')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='admin')}
19 ${self.menu_items(active='admin')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box main-content">
23 <div class="box main-content">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <!-- end box / title -->
28 <!-- end box / title -->
29 ${h.secure_form(h.url('users_groups'))}
29 ${h.secure_form(h.route_path('user_groups_create'), method='POST', request=request)}
30 <div class="form">
30 <div class="form">
31 <!-- fields -->
31 <!-- fields -->
32 <div class="fields">
32 <div class="fields">
33 <div class="field">
33 <div class="field">
34 <div class="label">
34 <div class="label">
35 <label for="users_group_name">${_('Group name')}:</label>
35 <label for="users_group_name">${_('Group name')}:</label>
36 </div>
36 </div>
37 <div class="input">
37 <div class="input">
38 ${h.text('users_group_name', class_='medium')}
38 ${h.text('users_group_name', class_='medium')}
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label for="user_group_description">${_('Description')}:</label>
43 <label for="user_group_description">${_('Description')}:</label>
44 </div>
44 </div>
45 <div class="textarea editor">
45 <div class="textarea editor">
46 ${h.textarea('user_group_description')}
46 ${h.textarea('user_group_description')}
47 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 <span class="help-block">${_('Short, optional description for this user group.')}</span>
48 </div>
48 </div>
49 </div>
49 </div>
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label for="users_group_active">${_('Active')}:</label>
52 <label for="users_group_active">${_('Active')}:</label>
53 </div>
53 </div>
54 <div class="checkboxes">
54 <div class="checkboxes">
55 ${h.checkbox('users_group_active',value=True, checked='checked')}
55 ${h.checkbox('users_group_active',value=True, checked='checked')}
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 <div class="buttons">
59 <div class="buttons">
60 ${h.submit('save',_('Save'),class_="btn")}
60 ${h.submit('save',_('Save'),class_="btn")}
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64 ${h.end_form()}
64 ${h.end_form()}
65 </div>
65 </div>
66 </%def>
66 </%def>
67
67
68 <script>
68 <script>
69 $(document).ready(function(){
69 $(document).ready(function(){
70 $('#users_group_name').focus();
70 $('#users_group_name').focus();
71 })
71 })
72 </script>
72 </script>
@@ -1,46 +1,46 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s user group settings') % c.user_group.users_group_name}
5 ${_('%s user group settings') % c.user_group.users_group_name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
14 ${h.link_to(_('User Groups'),h.route_path('user_groups'))}
15 &raquo;
15 &raquo;
16 ${c.user_group.users_group_name}
16 ${c.user_group.users_group_name}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28
28
29 ##main
29 ##main
30 <div class="sidebar-col-wrapper">
30 <div class="sidebar-col-wrapper">
31 <div class="sidebar">
31 <div class="sidebar">
32 <ul class="nav nav-pills nav-stacked">
32 <ul class="nav nav-pills nav-stacked">
33 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', user_group_id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
33 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.route_path('edit_user_group', user_group_id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', user_group_id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('edit_user_group_perms', user_group_id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.route_path('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.route_path('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.route_path('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
38 </ul>
38 </ul>
39 </div>
39 </div>
40
40
41 <div class="main-content-full-width">
41 <div class="main-content-full-width">
42 <%include file="/admin/user_groups/user_group_edit_${c.active}.mako"/>
42 <%include file="/admin/user_groups/user_group_edit_${c.active}.mako"/>
43 </div>
43 </div>
44 </div>
44 </div>
45 </div>
45 </div>
46 </%def>
46 </%def>
@@ -1,84 +1,84 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.user_group.user.email), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.user_group.user.email), '', ''),
6 (_('Created on'), h.format_date(c.user_group.created_on), '', '',),
6 (_('Created on'), h.format_date(c.user_group.created_on), '', '',),
7
7
8 (_('Members'), len(c.group_members_obj),'', [x for x in c.group_members_obj]),
8 (_('Members'), len(c.group_members_obj),'', [x for x in c.group_members_obj]),
9 (_('Automatic member sync'), 'Yes' if c.user_group.group_data.get('extern_type') else 'No', '', '',),
9 (_('Automatic member sync'), 'Yes' if c.user_group.group_data.get('extern_type') else 'No', '', '',),
10
10
11 (_('Assigned to repositories'), len(c.group_to_repos),'', [x for x in c.group_to_repos]),
11 (_('Assigned to repositories'), len(c.group_to_repos),'', [x for x in c.group_to_repos]),
12 (_('Assigned to repo groups'), len(c.group_to_repo_groups), '', [x for x in c.group_to_repo_groups]),
12 (_('Assigned to repo groups'), len(c.group_to_repo_groups), '', [x for x in c.group_to_repo_groups]),
13
13
14 (_('Assigned to review rules'), len(c.group_to_review_rules), '', [x for x in c.group_to_review_rules]),
14 (_('Assigned to review rules'), len(c.group_to_review_rules), '', [x for x in c.group_to_review_rules]),
15 ]
15 ]
16 %>
16 %>
17
17
18 <div class="panel panel-default">
18 <div class="panel panel-default">
19 <div class="panel-heading">
19 <div class="panel-heading">
20 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
20 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
21 </div>
21 </div>
22 <div class="panel-body">
22 <div class="panel-body">
23 ${base.dt_info_panel(elems)}
23 ${base.dt_info_panel(elems)}
24 </div>
24 </div>
25
25
26 </div>
26 </div>
27
27
28 <div class="panel panel-default">
28 <div class="panel panel-default">
29 <div class="panel-heading">
29 <div class="panel-heading">
30 <h3 class="panel-title">${_('Group members sync')}</h3>
30 <h3 class="panel-title">${_('Group members sync')}</h3>
31 </div>
31 </div>
32 <div class="panel-body">
32 <div class="panel-body">
33 <% sync_type = c.user_group.group_data.get('extern_type') %>
33 <% sync_type = c.user_group.group_data.get('extern_type') %>
34
34
35 % if sync_type:
35 % if sync_type:
36 <p>
36 <p>
37 ${_('This group is set to be automatically synchronised.')}<br/>
37 ${_('This group is set to be automatically synchronised.')}<br/>
38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
39 </p>
39 </p>
40 % else:
40 % else:
41 <p>
41 <p>
42 ${_('This group is not set to be automatically synchronised')}
42 ${_('This group is not set to be automatically synchronised')}
43 </p>
43 </p>
44 % endif
44 % endif
45
45
46 <div>
46 <div>
47 ${h.secure_form(h.url('edit_user_group_advanced_sync', user_group_id=c.user_group.users_group_id), method='post')}
47 ${h.secure_form(h.route_path('edit_user_group_advanced_sync', user_group_id=c.user_group.users_group_id), method='POST', request=request)}
48 <div class="field">
48 <div class="field">
49 <button class="btn btn-default" type="submit">
49 <button class="btn btn-default" type="submit">
50 %if sync_type:
50 %if sync_type:
51 ${_('Disable synchronization')}
51 ${_('Disable synchronization')}
52 %else:
52 %else:
53 ${_('Enable synchronization')}
53 ${_('Enable synchronization')}
54 %endif
54 %endif
55 </button>
55 </button>
56 </div>
56 </div>
57 <div class="field">
57 <div class="field">
58 <span class="help-block">
58 <span class="help-block">
59 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
59 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
60 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
60 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
61 </span>
61 </span>
62 </div>
62 </div>
63 ${h.end_form()}
63 ${h.end_form()}
64 </div>
64 </div>
65
65
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69
69
70 <div class="panel panel-danger">
70 <div class="panel panel-danger">
71 <div class="panel-heading">
71 <div class="panel-heading">
72 <h3 class="panel-title">${_('Delete User Group')}</h3>
72 <h3 class="panel-title">${_('Delete User Group')}</h3>
73 </div>
73 </div>
74 <div class="panel-body">
74 <div class="panel-body">
75 ${h.secure_form(h.url('delete_users_group', user_group_id=c.user_group.users_group_id),method='delete')}
75 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=c.user_group.users_group_id), method='POST', request=request)}
76 ${h.hidden('force', 1)}
76 ${h.hidden('force', 1)}
77 <button class="btn btn-small btn-danger" type="submit"
77 <button class="btn btn-small btn-danger" type="submit"
78 onclick="return confirm('${_('Confirm to delete user group `%(ugroup)s` with all permission assignments') % {'ugroup': c.user_group.users_group_name}}');">
78 onclick="return confirm('${_('Confirm to delete user group `%(ugroup)s` with all permission assignments') % {'ugroup': c.user_group.users_group_name}}');">
79 <i class="icon-remove-sign"></i>
79 <i class="icon-remove-sign"></i>
80 ${_('Delete This User Group')}
80 ${_('Delete This User Group')}
81 </button>
81 </button>
82 ${h.end_form()}
82 ${h.end_form()}
83 </div>
83 </div>
84 </div>
84 </div>
@@ -1,3 +1,3 b''
1 <%namespace name="dpb" file="/base/default_perms_box.mako"/>
1 <%namespace name="dpb" file="/base/default_perms_box.mako"/>
2 ${dpb.default_perms_box(h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id))}
2 ${dpb.default_perms_box(form_url=h.route_path('edit_user_group_global_perms_update', user_group_id=c.user_group.users_group_id))}
3
3
@@ -1,134 +1,134 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.url('edit_user_group_perms', user_group_id=c.user_group.users_group_id),method='put')}
8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), method='POST', request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th>${_('User/User Group')}</th>
15 <th>${_('User/User Group')}</th>
16 <th></th>
16 <th></th>
17 </tr>
17 </tr>
18 ## USERS
18 ## USERS
19 %for _user in c.user_group.permissions():
19 %for _user in c.user_group.permissions():
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 <tr class="perm_admin_row">
21 <tr class="perm_admin_row">
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-user">
26 <td class="td-user">
27 ${base.gravatar(_user.email, 16)}
27 ${base.gravatar(_user.email, 16)}
28 <span class="user">
28 <span class="user">
29 ${h.link_to_user(_user.username)}
29 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
30 %if getattr(_user, 'admin_row', None):
31 (${_('super admin')})
31 (${_('super admin')})
32 %endif
32 %endif
33 %if getattr(_user, 'owner_row', None):
33 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
34 (${_('owner')})
35 %endif
35 %endif
36 </span>
36 </span>
37 </td>
37 </td>
38 <td></td>
38 <td></td>
39 </tr>
39 </tr>
40 %else:
40 %else:
41 ##forbid revoking permission from yourself, except if you're an super admin
41 ##forbid revoking permission from yourself, except if you're an super admin
42 <tr>
42 <tr>
43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
48 <td class="td-user">
48 <td class="td-user">
49 ${base.gravatar(_user.email, 16)}
49 ${base.gravatar(_user.email, 16)}
50 <span class="user">
50 <span class="user">
51 % if _user.username == h.DEFAULT_USER:
51 % if _user.username == h.DEFAULT_USER:
52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 % else:
53 % else:
54 ${h.link_to_user(_user.username)}
54 ${h.link_to_user(_user.username)}
55 % endif
55 % endif
56 </span>
56 </span>
57 </td>
57 </td>
58 <td class="td-action">
58 <td class="td-action">
59 %if _user.username != h.DEFAULT_USER:
59 %if _user.username != h.DEFAULT_USER:
60 <span class="btn btn-link btn-danger revoke_perm"
60 <span class="btn btn-link btn-danger revoke_perm"
61 member="${_user.user_id}" member_type="user">
61 member="${_user.user_id}" member_type="user">
62 <i class="icon-remove"></i> ${_('revoke')}
62 <i class="icon-remove"></i> ${_('revoke')}
63 </span>
63 </span>
64 %endif
64 %endif
65 </td>
65 </td>
66 %else:
66 %else:
67 ## special case for current user permissions, we make sure he cannot take his own permissions
67 ## special case for current user permissions, we make sure he cannot take his own permissions
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
72 <td class="td-user">
72 <td class="td-user">
73 ${base.gravatar(_user.email, 16)}
73 ${base.gravatar(_user.email, 16)}
74 <span class="user">
74 <span class="user">
75 % if _user.username == h.DEFAULT_USER:
75 % if _user.username == h.DEFAULT_USER:
76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 % else:
77 % else:
78 ${h.link_to_user(_user.username)}
78 ${h.link_to_user(_user.username)}
79 % endif
79 % endif
80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 </span>
81 </span>
82 </td>
82 </td>
83 <td></td>
83 <td></td>
84 %endif
84 %endif
85 </tr>
85 </tr>
86 %endif
86 %endif
87 %endfor
87 %endfor
88
88
89 ## USER GROUPS
89 ## USER GROUPS
90 %for _user_group in c.user_group.permission_user_groups():
90 %for _user_group in c.user_group.permission_user_groups():
91 <tr>
91 <tr>
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
96 <td class="td-user">
96 <td class="td-user">
97 <i class="icon-group" ></i>
97 <i class="icon-group" ></i>
98 %if h.HasPermissionAny('hg.admin')():
98 %if h.HasPermissionAny('hg.admin')():
99 <a href="${h.url('edit_users_group',user_group_id=_user_group.users_group_id)}">
99 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
100 ${_user_group.users_group_name}
100 ${_user_group.users_group_name}
101 </a>
101 </a>
102 %else:
102 %else:
103 ${_user_group.users_group_name}
103 ${_user_group.users_group_name}
104 %endif
104 %endif
105 </td>
105 </td>
106 <td class="td-action">
106 <td class="td-action">
107 <span class="btn btn-link btn-danger revoke_perm"
107 <span class="btn btn-link btn-danger revoke_perm"
108 member="${_user_group.users_group_id}" member_type="user_group">
108 member="${_user_group.users_group_id}" member_type="user_group">
109 <i class="icon-remove"></i> ${_('revoke')}
109 <i class="icon-remove"></i> ${_('revoke')}
110 </span>
110 </span>
111 </td>
111 </td>
112 </tr>
112 </tr>
113 %endfor
113 %endfor
114 <tr class="new_members" id="add_perm_input"></tr>
114 <tr class="new_members" id="add_perm_input"></tr>
115 </table>
115 </table>
116 <div id="add_perm" class="link">
116 <div id="add_perm" class="link">
117 ${_('Add new')}
117 ${_('Add new')}
118 </div>
118 </div>
119 <div class="buttons">
119 <div class="buttons">
120 ${h.submit('save',_('Save'),class_="btn btn-primary")}
120 ${h.submit('save',_('Save'),class_="btn btn-primary")}
121 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
121 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
122 </div>
122 </div>
123 ${h.end_form()}
123 ${h.end_form()}
124 </div>
124 </div>
125 </div>
125 </div>
126
126
127 <script type="text/javascript">
127 <script type="text/javascript">
128 $('#add_perm').on('click', function(e){
128 $('#add_perm').on('click', function(e){
129 addNewPermInput($(this), 'usergroup');
129 addNewPermInput($(this), 'usergroup');
130 });
130 });
131 $('.revoke_perm').on('click', function(e){
131 $('.revoke_perm').on('click', function(e){
132 markRevokePermInput($(this), 'usergroup');
132 markRevokePermInput($(this), 'usergroup');
133 });
133 });
134 </script>
134 </script>
@@ -1,186 +1,186 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
9 ${h.secure_form(h.route_path('user_groups_update', user_group_id=c.user_group.users_group_id), id='edit_user_group', method='POST', request=request)}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="users_group_name">${_('Group name')}:</label>
15 <label for="users_group_name">${_('Group name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('users_group_name',class_='medium')}
18 ${h.text('users_group_name',class_='medium')}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field badged-field">
22 <div class="field badged-field">
23 <div class="label">
23 <div class="label">
24 <label for="user">${_('Owner')}:</label>
24 <label for="user">${_('Owner')}:</label>
25 </div>
25 </div>
26 <div class="input">
26 <div class="input">
27 <div class="badge-input-container">
27 <div class="badge-input-container">
28 <div class="user-badge">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 </div>
30 </div>
31 <div class="badge-input-wrap">
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <form:error name="user"/>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="user_group_description">${_('Description')}:</label>
42 <label for="user_group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea textarea-small editor">
44 <div class="textarea textarea-small editor">
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox">
50 <div class="label label-checkbox">
51 <label for="users_group_active">${_('Active')}:</label>
51 <label for="users_group_active">${_('Active')}:</label>
52 </div>
52 </div>
53 <div class="checkboxes">
53 <div class="checkboxes">
54 ${h.checkbox('users_group_active',value=True)}
54 ${h.checkbox('users_group_active',value=True)}
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label for="users_group_active">${_('Add members')}:</label>
60 <label for="users_group_active">${_('Add members')}:</label>
61 </div>
61 </div>
62 <div class="input">
62 <div class="input">
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
68 <table id="group_members_placeholder" class="rctable group_members">
68 <table id="group_members_placeholder" class="rctable group_members">
69 <tr>
69 <tr>
70 <th>${_('Username')}</th>
70 <th>${_('Username')}</th>
71 <th>${_('Action')}</th>
71 <th>${_('Action')}</th>
72 </tr>
72 </tr>
73
73
74 % if c.group_members_obj:
74 % if c.group_members_obj:
75 % for user in c.group_members_obj:
75 % for user in c.group_members_obj:
76 <tr>
76 <tr>
77 <td id="member_user_${user.user_id}" class="td-author">
77 <td id="member_user_${user.user_id}" class="td-author">
78 <div class="group_member">
78 <div class="group_member">
79 ${base.gravatar(user.email, 16)}
79 ${base.gravatar(user.email, 16)}
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
81 <input type="hidden" name="__start__" value="member:mapping">
81 <input type="hidden" name="__start__" value="member:mapping">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
84 <input type="hidden" name="__end__" value="member:mapping">
84 <input type="hidden" name="__end__" value="member:mapping">
85 </div>
85 </div>
86 </td>
86 </td>
87 <td class="">
87 <td class="">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
89 <i class="icon-remove-sign"></i>
89 <i class="icon-remove-sign"></i>
90 </div>
90 </div>
91 </td>
91 </td>
92 </tr>
92 </tr>
93 % endfor
93 % endfor
94
94
95 % else:
95 % else:
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
97 % endif
97 % endif
98 </table>
98 </table>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
100
100
101 <div class="buttons">
101 <div class="buttons">
102 ${h.submit('Save',_('Save'),class_="btn")}
102 ${h.submit('Save',_('Save'),class_="btn")}
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 ${h.end_form()}
106 ${h.end_form()}
107 </div>
107 </div>
108 </div>
108 </div>
109 <script>
109 <script>
110 $(document).ready(function(){
110 $(document).ready(function(){
111 $("#group_parent_id").select2({
111 $("#group_parent_id").select2({
112 'containerCssClass': "drop-menu",
112 'containerCssClass': "drop-menu",
113 'dropdownCssClass': "drop-menu-dropdown",
113 'dropdownCssClass': "drop-menu-dropdown",
114 'dropdownAutoWidth': true
114 'dropdownAutoWidth': true
115 });
115 });
116
116
117 removeUserGroupMember = function(userId){
117 removeUserGroupMember = function(userId){
118 $('#member_'+userId).val('remove');
118 $('#member_'+userId).val('remove');
119 $('#member_user_'+userId).addClass('to-delete');
119 $('#member_user_'+userId).addClass('to-delete');
120 };
120 };
121
121
122 $('#user_group_add_members').autocomplete({
122 $('#user_group_add_members').autocomplete({
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
124 minChars:2,
124 minChars:2,
125 maxHeight:400,
125 maxHeight:400,
126 width:300,
126 width:300,
127 deferRequestBy: 300, //miliseconds
127 deferRequestBy: 300, //miliseconds
128 showNoSuggestionNotice: true,
128 showNoSuggestionNotice: true,
129 params: { user_groups:true },
129 params: { user_groups:true },
130 formatResult: autocompleteFormatResult,
130 formatResult: autocompleteFormatResult,
131 lookupFilter: autocompleteFilterResult,
131 lookupFilter: autocompleteFilterResult,
132 onSelect: function(element, suggestion){
132 onSelect: function(element, suggestion){
133
133
134 function addMember(user, fromUserGroup) {
134 function addMember(user, fromUserGroup) {
135 var gravatar = user.icon_link;
135 var gravatar = user.icon_link;
136 var username = user.value_display;
136 var username = user.value_display;
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
138 var uid = user.id;
138 var uid = user.id;
139
139
140 if (fromUserGroup) {
140 if (fromUserGroup) {
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
142 }
142 }
143
143
144 var elem = $(
144 var elem = $(
145 ('<tr>'+
145 ('<tr>'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
147 '<div class="group_member">'+
147 '<div class="group_member">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
154 '</div>'+
154 '</div>'+
155 '</td>'+
155 '</td>'+
156 '<td class="td-author-new-entry">'+
156 '<td class="td-author-new-entry">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
158 '<i class="icon-remove-sign"></i>'+
158 '<i class="icon-remove-sign"></i>'+
159 '</div>'+
159 '</div>'+
160 '</td>'+
160 '</td>'+
161 '</tr>').format(gravatar, userLink, username,
161 '</tr>').format(gravatar, userLink, username,
162 uid, uid, uid, uid)
162 uid, uid, uid, uid)
163 );
163 );
164 $('#group_members_placeholder').append(elem)
164 $('#group_members_placeholder').append(elem)
165 }
165 }
166
166
167 if (suggestion.value_type == 'user_group') {
167 if (suggestion.value_type == 'user_group') {
168 $.getJSON(
168 $.getJSON(
169 pyroutes.url('user_group_members_data',
169 pyroutes.url('user_group_members_data',
170 {'user_group_id': suggestion.id}),
170 {'user_group_id': suggestion.id}),
171 function(data) {
171 function(data) {
172 $.each(data.members, function(idx, user) {
172 $.each(data.members, function(idx, user) {
173 addMember(user, suggestion.value)
173 addMember(user, suggestion.value)
174 });
174 });
175 }
175 }
176 );
176 );
177 } else if (suggestion.value_type == 'user') {
177 } else if (suggestion.value_type == 'user') {
178 addMember(suggestion, null);
178 addMember(suggestion, null);
179 }
179 }
180 }
180 }
181 });
181 });
182
182
183
183
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
185 })
185 })
186 </script>
186 </script>
@@ -1,108 +1,108 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('User groups administration')}
5 ${_('User groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 <ul class="links">
25 <ul class="links">
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 <li>
27 <li>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
28 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 </li>
29 </li>
30 %endif
30 %endif
31 </ul>
31 </ul>
32 </div>
32 </div>
33
33
34 <div id="repos_list_wrap">
34 <div id="repos_list_wrap">
35 <table id="user_group_list_table" class="display"></table>
35 <table id="user_group_list_table" class="display"></table>
36 </div>
36 </div>
37
37
38 </div>
38 </div>
39 <script>
39 <script>
40 $(document).ready(function() {
40 $(document).ready(function() {
41 var getDatatableCount = function(){
41 var getDatatableCount = function(){
42 var table = $('#user_group_list_table').dataTable();
42 var table = $('#user_group_list_table').dataTable();
43 var page = table.api().page.info();
43 var page = table.api().page.info();
44 var active = page.recordsDisplay;
44 var active = page.recordsDisplay;
45 var total = page.recordsTotal;
45 var total = page.recordsTotal;
46
46
47 var _text = _gettext("{0} out of {1} users").format(active, total);
47 var _text = _gettext("{0} out of {1} users").format(active, total);
48 $('#user_group_count').text(_text);
48 $('#user_group_count').text(_text);
49 };
49 };
50
50
51 // user list
51 // user list
52 $('#user_group_list_table').DataTable({
52 $('#user_group_list_table').DataTable({
53 processing: true,
53 processing: true,
54 serverSide: true,
54 serverSide: true,
55 ajax: "${h.route_path('user_groups_data')}",
55 ajax: "${h.route_path('user_groups_data')}",
56 dom: 'rtp',
56 dom: 'rtp',
57 pageLength: ${c.visual.admin_grid_items},
57 pageLength: ${c.visual.admin_grid_items},
58 order: [[ 0, "asc" ]],
58 order: [[ 0, "asc" ]],
59 columns: [
59 columns: [
60 { data: {"_": "users_group_name",
60 { data: {"_": "users_group_name",
61 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
61 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
62 { data: {"_": "description",
62 { data: {"_": "description",
63 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
63 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
64 { data: {"_": "members",
64 { data: {"_": "members",
65 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
65 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
66 { data: {"_": "sync",
66 { data: {"_": "sync",
67 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
67 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
68 { data: {"_": "active",
68 { data: {"_": "active",
69 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
69 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
70 { data: {"_": "owner",
70 { data: {"_": "owner",
71 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
71 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
72 { data: {"_": "action",
72 { data: {"_": "action",
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
74 ],
74 ],
75 language: {
75 language: {
76 paginate: DEFAULT_GRID_PAGINATION,
76 paginate: DEFAULT_GRID_PAGINATION,
77 sProcessing: _gettext('loading...'),
77 sProcessing: _gettext('loading...'),
78 emptyTable: _gettext("No user groups available yet.")
78 emptyTable: _gettext("No user groups available yet.")
79 }
79 }
80 });
80 });
81
81
82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
83 $('#user_group_list_table').css('opacity', 1);
83 $('#user_group_list_table').css('opacity', 1);
84 });
84 });
85
85
86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
87 $('#user_group_list_table').css('opacity', 0.3);
87 $('#user_group_list_table').css('opacity', 0.3);
88 });
88 });
89
89
90 // refresh counters on draw
90 // refresh counters on draw
91 $('#user_group_list_table').on('draw.dt', function(){
91 $('#user_group_list_table').on('draw.dt', function(){
92 getDatatableCount();
92 getDatatableCount();
93 });
93 });
94
94
95 // filter
95 // filter
96 $('#q_filter').on('keyup',
96 $('#q_filter').on('keyup',
97 $.debounce(250, function() {
97 $.debounce(250, function() {
98 $('#user_group_list_table').DataTable().search(
98 $('#user_group_list_table').DataTable().search(
99 $('#q_filter').val()
99 $('#q_filter').val()
100 ).draw();
100 ).draw();
101 })
101 })
102 );
102 );
103
103
104 });
104 });
105
105
106 </script>
106 </script>
107
107
108 </%def>
108 </%def>
@@ -1,609 +1,609 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
234 <li class="${is_active('showpullrequest')}">
234 <li class="${is_active('showpullrequest')}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
236 %if c.repository_pull_requests:
236 %if c.repository_pull_requests:
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
238 %endif
238 %endif
239 <div class="menulabel">${_('Pull Requests')}</div>
239 <div class="menulabel">${_('Pull Requests')}</div>
240 </a>
240 </a>
241 </li>
241 </li>
242 %endif
242 %endif
243 <li class="${is_active('options')}">
243 <li class="${is_active('options')}">
244 <a class="menulink dropdown">
244 <a class="menulink dropdown">
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
246 </a>
246 </a>
247 <ul class="submenu">
247 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
250 %endif
251 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
252 <li>
252 <li>
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 href="${h.route_path('repo_compare',
254 href="${h.route_path('repo_compare',
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 _query=dict(merge=1))}"
260 _query=dict(merge=1))}"
261 >
261 >
262 ${_('Compare fork')}
262 ${_('Compare fork')}
263 </a>
263 </a>
264 </li>
264 </li>
265 %endif
265 %endif
266
266
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
268
268
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
270 %if c.rhodecode_db_repo.locked[0]:
270 %if c.rhodecode_db_repo.locked[0]:
271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
272 %else:
272 %else:
273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
274 %endif
274 %endif
275 %endif
275 %endif
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 %endif
280 %endif
281 %endif
281 %endif
282 </ul>
282 </ul>
283 </li>
283 </li>
284 </ul>
284 </ul>
285 </div>
285 </div>
286 <div class="clear"></div>
286 <div class="clear"></div>
287 </div>
287 </div>
288 <!--- END CONTEXT BAR -->
288 <!--- END CONTEXT BAR -->
289
289
290 </%def>
290 </%def>
291
291
292 <%def name="usermenu(active=False)">
292 <%def name="usermenu(active=False)">
293 ## USER MENU
293 ## USER MENU
294 <li id="quick_login_li" class="${'active' if active else ''}">
294 <li id="quick_login_li" class="${'active' if active else ''}">
295 <a id="quick_login_link" class="menulink childs">
295 <a id="quick_login_link" class="menulink childs">
296 ${gravatar(c.rhodecode_user.email, 20)}
296 ${gravatar(c.rhodecode_user.email, 20)}
297 <span class="user">
297 <span class="user">
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
300 %else:
300 %else:
301 <span>${_('Sign in')}</span>
301 <span>${_('Sign in')}</span>
302 %endif
302 %endif
303 </span>
303 </span>
304 </a>
304 </a>
305
305
306 <div class="user-menu submenu">
306 <div class="user-menu submenu">
307 <div id="quick_login">
307 <div id="quick_login">
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
309 <h4>${_('Sign in to your account')}</h4>
309 <h4>${_('Sign in to your account')}</h4>
310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
311 <div class="form form-vertical">
311 <div class="form form-vertical">
312 <div class="fields">
312 <div class="fields">
313 <div class="field">
313 <div class="field">
314 <div class="label">
314 <div class="label">
315 <label for="username">${_('Username')}:</label>
315 <label for="username">${_('Username')}:</label>
316 </div>
316 </div>
317 <div class="input">
317 <div class="input">
318 ${h.text('username',class_='focus',tabindex=1)}
318 ${h.text('username',class_='focus',tabindex=1)}
319 </div>
319 </div>
320
320
321 </div>
321 </div>
322 <div class="field">
322 <div class="field">
323 <div class="label">
323 <div class="label">
324 <label for="password">${_('Password')}:</label>
324 <label for="password">${_('Password')}:</label>
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
327 %endif
327 %endif
328 </div>
328 </div>
329 <div class="input">
329 <div class="input">
330 ${h.password('password',class_='focus',tabindex=2)}
330 ${h.password('password',class_='focus',tabindex=2)}
331 </div>
331 </div>
332 </div>
332 </div>
333 <div class="buttons">
333 <div class="buttons">
334 <div class="register">
334 <div class="register">
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
337 %endif
337 %endif
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
339 </div>
339 </div>
340 <div class="submit">
340 <div class="submit">
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
342 </div>
342 </div>
343 </div>
343 </div>
344 </div>
344 </div>
345 </div>
345 </div>
346 ${h.end_form()}
346 ${h.end_form()}
347 %else:
347 %else:
348 <div class="">
348 <div class="">
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
352 </div>
352 </div>
353 <div class="">
353 <div class="">
354 <ol class="links">
354 <ol class="links">
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
356 % if c.rhodecode_user.personal_repo_group:
356 % if c.rhodecode_user.personal_repo_group:
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
358 % endif
358 % endif
359 <li class="logout">
359 <li class="logout">
360 ${h.secure_form(h.route_path('logout'), request=request)}
360 ${h.secure_form(h.route_path('logout'), request=request)}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
362 ${h.end_form()}
362 ${h.end_form()}
363 </li>
363 </li>
364 </ol>
364 </ol>
365 </div>
365 </div>
366 %endif
366 %endif
367 </div>
367 </div>
368 </div>
368 </div>
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
370 <div class="pill_container">
370 <div class="pill_container">
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
372 </div>
372 </div>
373 % endif
373 % endif
374 </li>
374 </li>
375 </%def>
375 </%def>
376
376
377 <%def name="menu_items(active=None)">
377 <%def name="menu_items(active=None)">
378 <%
378 <%
379 def is_active(selected):
379 def is_active(selected):
380 if selected == active:
380 if selected == active:
381 return "active"
381 return "active"
382 return ""
382 return ""
383 %>
383 %>
384 <ul id="quick" class="main_nav navigation horizontal-list">
384 <ul id="quick" class="main_nav navigation horizontal-list">
385 <!-- repo switcher -->
385 <!-- repo switcher -->
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
388 </li>
388 </li>
389
389
390 ## ROOT MENU
390 ## ROOT MENU
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 <li class="${is_active('journal')}">
392 <li class="${is_active('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
394 <div class="menulabel">${_('Journal')}</div>
394 <div class="menulabel">${_('Journal')}</div>
395 </a>
395 </a>
396 </li>
396 </li>
397 %else:
397 %else:
398 <li class="${is_active('journal')}">
398 <li class="${is_active('journal')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
400 <div class="menulabel">${_('Public journal')}</div>
400 <div class="menulabel">${_('Public journal')}</div>
401 </a>
401 </a>
402 </li>
402 </li>
403 %endif
403 %endif
404 <li class="${is_active('gists')}">
404 <li class="${is_active('gists')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
406 <div class="menulabel">${_('Gists')}</div>
406 <div class="menulabel">${_('Gists')}</div>
407 </a>
407 </a>
408 </li>
408 </li>
409 <li class="${is_active('search')}">
409 <li class="${is_active('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
411 <div class="menulabel">${_('Search')}</div>
411 <div class="menulabel">${_('Search')}</div>
412 </a>
412 </a>
413 </li>
413 </li>
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
415 <li class="${is_active('admin')}">
415 <li class="${is_active('admin')}">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 </a>
418 </a>
419 ${admin_menu()}
419 ${admin_menu()}
420 </li>
420 </li>
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
422 <li class="${is_active('admin')}">
422 <li class="${is_active('admin')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
425 </a>
425 </a>
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
427 c.rhodecode_user.repository_groups_admin,
427 c.rhodecode_user.repository_groups_admin,
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
429 </li>
429 </li>
430 % endif
430 % endif
431 % if c.debug_style:
431 % if c.debug_style:
432 <li class="${is_active('debug_style')}">
432 <li class="${is_active('debug_style')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
434 <div class="menulabel">${_('Style')}</div>
434 <div class="menulabel">${_('Style')}</div>
435 </a>
435 </a>
436 </li>
436 </li>
437 % endif
437 % endif
438 ## render extra user menu
438 ## render extra user menu
439 ${usermenu(active=(active=='my_account'))}
439 ${usermenu(active=(active=='my_account'))}
440 </ul>
440 </ul>
441
441
442 <script type="text/javascript">
442 <script type="text/javascript">
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
444
444
445 /*format the look of items in the list*/
445 /*format the look of items in the list*/
446 var format = function(state, escapeMarkup){
446 var format = function(state, escapeMarkup){
447 if (!state.id){
447 if (!state.id){
448 return state.text; // optgroup
448 return state.text; // optgroup
449 }
449 }
450 var obj_dict = state.obj;
450 var obj_dict = state.obj;
451 var tmpl = '';
451 var tmpl = '';
452
452
453 if(obj_dict && state.type == 'repo'){
453 if(obj_dict && state.type == 'repo'){
454 if(obj_dict['repo_type'] === 'hg'){
454 if(obj_dict['repo_type'] === 'hg'){
455 tmpl += '<i class="icon-hg"></i> ';
455 tmpl += '<i class="icon-hg"></i> ';
456 }
456 }
457 else if(obj_dict['repo_type'] === 'git'){
457 else if(obj_dict['repo_type'] === 'git'){
458 tmpl += '<i class="icon-git"></i> ';
458 tmpl += '<i class="icon-git"></i> ';
459 }
459 }
460 else if(obj_dict['repo_type'] === 'svn'){
460 else if(obj_dict['repo_type'] === 'svn'){
461 tmpl += '<i class="icon-svn"></i> ';
461 tmpl += '<i class="icon-svn"></i> ';
462 }
462 }
463 if(obj_dict['private']){
463 if(obj_dict['private']){
464 tmpl += '<i class="icon-lock" ></i> ';
464 tmpl += '<i class="icon-lock" ></i> ';
465 }
465 }
466 else if(visual_show_public_icon){
466 else if(visual_show_public_icon){
467 tmpl += '<i class="icon-unlock-alt"></i> ';
467 tmpl += '<i class="icon-unlock-alt"></i> ';
468 }
468 }
469 }
469 }
470 if(obj_dict && state.type == 'commit') {
470 if(obj_dict && state.type == 'commit') {
471 tmpl += '<i class="icon-tag"></i>';
471 tmpl += '<i class="icon-tag"></i>';
472 }
472 }
473 if(obj_dict && state.type == 'group'){
473 if(obj_dict && state.type == 'group'){
474 tmpl += '<i class="icon-folder-close"></i> ';
474 tmpl += '<i class="icon-folder-close"></i> ';
475 }
475 }
476 tmpl += escapeMarkup(state.text);
476 tmpl += escapeMarkup(state.text);
477 return tmpl;
477 return tmpl;
478 };
478 };
479
479
480 var formatResult = function(result, container, query, escapeMarkup) {
480 var formatResult = function(result, container, query, escapeMarkup) {
481 return format(result, escapeMarkup);
481 return format(result, escapeMarkup);
482 };
482 };
483
483
484 var formatSelection = function(data, container, escapeMarkup) {
484 var formatSelection = function(data, container, escapeMarkup) {
485 return format(data, escapeMarkup);
485 return format(data, escapeMarkup);
486 };
486 };
487
487
488 $("#repo_switcher").select2({
488 $("#repo_switcher").select2({
489 cachedDataSource: {},
489 cachedDataSource: {},
490 minimumInputLength: 2,
490 minimumInputLength: 2,
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
492 dropdownAutoWidth: true,
492 dropdownAutoWidth: true,
493 formatResult: formatResult,
493 formatResult: formatResult,
494 formatSelection: formatSelection,
494 formatSelection: formatSelection,
495 containerCssClass: "repo-switcher",
495 containerCssClass: "repo-switcher",
496 dropdownCssClass: "repo-switcher-dropdown",
496 dropdownCssClass: "repo-switcher-dropdown",
497 escapeMarkup: function(m){
497 escapeMarkup: function(m){
498 // don't escape our custom placeholder
498 // don't escape our custom placeholder
499 if(m.substr(0,23) == '<div class="menulabel">'){
499 if(m.substr(0,23) == '<div class="menulabel">'){
500 return m;
500 return m;
501 }
501 }
502
502
503 return Select2.util.escapeMarkup(m);
503 return Select2.util.escapeMarkup(m);
504 },
504 },
505 query: $.debounce(250, function(query){
505 query: $.debounce(250, function(query){
506 self = this;
506 self = this;
507 var cacheKey = query.term;
507 var cacheKey = query.term;
508 var cachedData = self.cachedDataSource[cacheKey];
508 var cachedData = self.cachedDataSource[cacheKey];
509
509
510 if (cachedData) {
510 if (cachedData) {
511 query.callback({results: cachedData.results});
511 query.callback({results: cachedData.results});
512 } else {
512 } else {
513 $.ajax({
513 $.ajax({
514 url: pyroutes.url('goto_switcher_data'),
514 url: pyroutes.url('goto_switcher_data'),
515 data: {'query': query.term},
515 data: {'query': query.term},
516 dataType: 'json',
516 dataType: 'json',
517 type: 'GET',
517 type: 'GET',
518 success: function(data) {
518 success: function(data) {
519 self.cachedDataSource[cacheKey] = data;
519 self.cachedDataSource[cacheKey] = data;
520 query.callback({results: data.results});
520 query.callback({results: data.results});
521 },
521 },
522 error: function(data, textStatus, errorThrown) {
522 error: function(data, textStatus, errorThrown) {
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
524 }
524 }
525 })
525 })
526 }
526 }
527 })
527 })
528 });
528 });
529
529
530 $("#repo_switcher").on('select2-selecting', function(e){
530 $("#repo_switcher").on('select2-selecting', function(e){
531 e.preventDefault();
531 e.preventDefault();
532 window.location = e.choice.url;
532 window.location = e.choice.url;
533 });
533 });
534
534
535 </script>
535 </script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
537 </%def>
537 </%def>
538
538
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
540 <div class="modal-dialog">
540 <div class="modal-dialog">
541 <div class="modal-content">
541 <div class="modal-content">
542 <div class="modal-header">
542 <div class="modal-header">
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
545 </div>
545 </div>
546 <div class="modal-body">
546 <div class="modal-body">
547 <div class="block-left">
547 <div class="block-left">
548 <table class="keyboard-mappings">
548 <table class="keyboard-mappings">
549 <tbody>
549 <tbody>
550 <tr>
550 <tr>
551 <th></th>
551 <th></th>
552 <th>${_('Site-wide shortcuts')}</th>
552 <th>${_('Site-wide shortcuts')}</th>
553 </tr>
553 </tr>
554 <%
554 <%
555 elems = [
555 elems = [
556 ('/', 'Open quick search box'),
556 ('/', 'Open quick search box'),
557 ('g h', 'Goto home page'),
557 ('g h', 'Goto home page'),
558 ('g g', 'Goto my private gists page'),
558 ('g g', 'Goto my private gists page'),
559 ('g G', 'Goto my public gists page'),
559 ('g G', 'Goto my public gists page'),
560 ('n r', 'New repository page'),
560 ('n r', 'New repository page'),
561 ('n g', 'New gist page'),
561 ('n g', 'New gist page'),
562 ]
562 ]
563 %>
563 %>
564 %for key, desc in elems:
564 %for key, desc in elems:
565 <tr>
565 <tr>
566 <td class="keys">
566 <td class="keys">
567 <span class="key tag">${key}</span>
567 <span class="key tag">${key}</span>
568 </td>
568 </td>
569 <td>${desc}</td>
569 <td>${desc}</td>
570 </tr>
570 </tr>
571 %endfor
571 %endfor
572 </tbody>
572 </tbody>
573 </table>
573 </table>
574 </div>
574 </div>
575 <div class="block-left">
575 <div class="block-left">
576 <table class="keyboard-mappings">
576 <table class="keyboard-mappings">
577 <tbody>
577 <tbody>
578 <tr>
578 <tr>
579 <th></th>
579 <th></th>
580 <th>${_('Repositories')}</th>
580 <th>${_('Repositories')}</th>
581 </tr>
581 </tr>
582 <%
582 <%
583 elems = [
583 elems = [
584 ('g s', 'Goto summary page'),
584 ('g s', 'Goto summary page'),
585 ('g c', 'Goto changelog page'),
585 ('g c', 'Goto changelog page'),
586 ('g f', 'Goto files page'),
586 ('g f', 'Goto files page'),
587 ('g F', 'Goto files page with file search activated'),
587 ('g F', 'Goto files page with file search activated'),
588 ('g p', 'Goto pull requests page'),
588 ('g p', 'Goto pull requests page'),
589 ('g o', 'Goto repository settings'),
589 ('g o', 'Goto repository settings'),
590 ('g O', 'Goto repository permissions settings'),
590 ('g O', 'Goto repository permissions settings'),
591 ]
591 ]
592 %>
592 %>
593 %for key, desc in elems:
593 %for key, desc in elems:
594 <tr>
594 <tr>
595 <td class="keys">
595 <td class="keys">
596 <span class="key tag">${key}</span>
596 <span class="key tag">${key}</span>
597 </td>
597 </td>
598 <td>${desc}</td>
598 <td>${desc}</td>
599 </tr>
599 </tr>
600 %endfor
600 %endfor
601 </tbody>
601 </tbody>
602 </table>
602 </table>
603 </div>
603 </div>
604 </div>
604 </div>
605 <div class="modal-footer">
605 <div class="modal-footer">
606 </div>
606 </div>
607 </div><!-- /.modal-content -->
607 </div><!-- /.modal-content -->
608 </div><!-- /.modal-dialog -->
608 </div><!-- /.modal-dialog -->
609 </div><!-- /.modal -->
609 </div><!-- /.modal -->
@@ -1,156 +1,156 b''
1 ## snippet for displaying default permission box
1 ## snippet for displaying default permission box
2 ## usage:
2 ## usage:
3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
4 ## ${dpb.default_perms_box(<url_to_form>)}
4 ## ${dpb.default_perms_box(<url_to_form>)}
5 ## ${dpb.default_perms_radios()}
5 ## ${dpb.default_perms_radios()}
6
6
7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
8 <div class="main-content-full-width">
8 <div class="main-content-full-width">
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10
10
11 ## displayed according to checkbox selection
11 ## displayed according to checkbox selection
12 <div class="panel-heading">
12 <div class="panel-heading">
13 %if not global_permissions_template:
13 %if not global_permissions_template:
14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
16 %else:
16 %else:
17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
18 %endif
18 %endif
19 </div>
19 </div>
20
20
21 <div class="panel-body">
21 <div class="panel-body">
22 %if global_permissions_template:
22 %if global_permissions_template:
23 <p>${_('The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings.')}</p>
23 <p>${_('The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings.')}</p>
24 %endif
24 %endif
25 <div class="field">
25 <div class="field">
26 <div class="label">
26 <div class="label">
27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
28 </div>
28 </div>
29 <div class="radios">
29 <div class="radios">
30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
32 <span class="help-block">${_('Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups.')}</span>
32 <span class="help-block">${_('Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups.')}</span>
33 </div>
33 </div>
34 </div>
34 </div>
35 <div class="field">
35 <div class="field">
36 <div class="label">
36 <div class="label">
37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
38 </div>
38 </div>
39 <div class="radios">
39 <div class="radios">
40 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[1][0], label=c.repo_create_on_write_choices[1][1], **kwargs)}
40 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[1][0], label=c.repo_create_on_write_choices[1][1], **kwargs)}
41 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[0][0], label=c.repo_create_on_write_choices[0][1], **kwargs)}
41 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[0][0], label=c.repo_create_on_write_choices[0][1], **kwargs)}
42 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
42 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
43 </div>
43 </div>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
48 </div>
48 </div>
49 <div class="radios">
49 <div class="radios">
50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
52 <span class="help-block">${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')}</span>
52 <span class="help-block">${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')}</span>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
58 </div>
58 </div>
59 <div class="radios">
59 <div class="radios">
60 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[1][0], label=c.repo_group_create_choices[1][1], **kwargs)}
60 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[1][0], label=c.repo_group_create_choices[1][1], **kwargs)}
61 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[0][0], label=c.repo_group_create_choices[0][1], **kwargs)}
61 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[0][0], label=c.repo_group_create_choices[0][1], **kwargs)}
62 <span class="help-block">${_('Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups.')}</span>
62 <span class="help-block">${_('Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups.')}</span>
63 </div>
63 </div>
64 </div>
64 </div>
65 <div class="field">
65 <div class="field">
66 <div class="label">
66 <div class="label">
67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
68 </div>
68 </div>
69 <div class="radios">
69 <div class="radios">
70 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[1][0], label=c.user_group_create_choices[1][1], **kwargs)}
70 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[1][0], label=c.user_group_create_choices[1][1], **kwargs)}
71 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[0][0], label=c.user_group_create_choices[0][1], **kwargs)}
71 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[0][0], label=c.user_group_create_choices[0][1], **kwargs)}
72 <span class="help-block">${_('Permission to allow user group creation.')}</span>
72 <span class="help-block">${_('Permission to allow user group creation.')}</span>
73 </div>
73 </div>
74 </div>
74 </div>
75
75
76 <div class="field">
76 <div class="field">
77 <div class="label">
77 <div class="label">
78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
79 </div>
79 </div>
80 <div class="radios">
80 <div class="radios">
81 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[1][0], label=c.inherit_default_permission_choices[1][1], **kwargs)}
81 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[1][0], label=c.inherit_default_permission_choices[1][1], **kwargs)}
82 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[0][0], label=c.inherit_default_permission_choices[0][1], **kwargs)}
82 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[0][0], label=c.inherit_default_permission_choices[0][1], **kwargs)}
83 <span class="help-block">${_('Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions.')}</span>
83 <span class="help-block">${_('Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions.')}</span>
84 </div>
84 </div>
85 </div>
85 </div>
86
86
87 <div class="buttons">
87 <div class="buttons">
88 ${h.submit('save',_('Save'),class_="btn")}
88 ${h.submit('save',_('Save'),class_="btn")}
89 ${h.reset('reset',_('Reset'),class_="btn")}
89 ${h.reset('reset',_('Reset'),class_="btn")}
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 </%def>
94 </%def>
95
95
96 <%def name="default_perms_box(form_url)">
96 <%def name="default_perms_box(form_url)">
97 ${h.secure_form(form_url, method='put')}
97 ${h.secure_form(form_url, method='POST', request=request)}
98 <div class="form">
98 <div class="form">
99 <div class="fields">
99 <div class="fields">
100 <div class="field panel panel-default panel-body">
100 <div class="field panel panel-default panel-body">
101 <div class="label label-checkbox">
101 <div class="label label-checkbox">
102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
103 </div>
103 </div>
104 <div class="checkboxes">
104 <div class="checkboxes">
105 ${h.checkbox('inherit_default_permissions',value=True)}
105 ${h.checkbox('inherit_default_permissions',value=True)}
106 <span class="help-block">
106 <span class="help-block">
107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
108 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
108 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
109 % h.link_to('default user', h.route_path('admin_permissions_global')))}
109 % h.link_to('default user', h.route_path('admin_permissions_global')))}
110 </span>
110 </span>
111 </div>
111 </div>
112 </div>
112 </div>
113
113
114 ## INHERITED permissions == the user permissions in admin
114 ## INHERITED permissions == the user permissions in admin
115 ## if inherit checkbox is set this is displayed in non-edit mode
115 ## if inherit checkbox is set this is displayed in non-edit mode
116 <div class="inherit_overlay_default">
116 <div class="inherit_overlay_default">
117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
118 </div>
118 </div>
119
119
120 ## CUSTOM permissions
120 ## CUSTOM permissions
121 <div class="inherit_overlay">
121 <div class="inherit_overlay">
122 ${default_perms_radios(global_permissions_template = False)}
122 ${default_perms_radios(global_permissions_template = False)}
123 </div>
123 </div>
124 </div>
124 </div>
125 </div>
125 </div>
126 ${h.end_form()}
126 ${h.end_form()}
127
127
128
128
129 ## JS
129 ## JS
130 <script>
130 <script>
131 var show_custom_perms = function(inherit_default){
131 var show_custom_perms = function(inherit_default){
132 if(inherit_default) {
132 if(inherit_default) {
133 $('.inherit_overlay_default').show();
133 $('.inherit_overlay_default').show();
134 $('.inherit_overlay').hide();
134 $('.inherit_overlay').hide();
135 }
135 }
136 else {
136 else {
137 $('.inherit_overlay').show();
137 $('.inherit_overlay').show();
138 $('.inherit_overlay_default').hide();
138 $('.inherit_overlay_default').hide();
139 }
139 }
140 };
140 };
141 $(document).ready(function(e){
141 $(document).ready(function(e){
142 var inherit_checkbox = $('#inherit_default_permissions');
142 var inherit_checkbox = $('#inherit_default_permissions');
143 var defaults = inherit_checkbox.prop('checked');
143 var defaults = inherit_checkbox.prop('checked');
144 show_custom_perms(defaults);
144 show_custom_perms(defaults);
145 inherit_checkbox.on('change', function(){
145 inherit_checkbox.on('change', function(){
146 if($(this).prop('checked')){
146 if($(this).prop('checked')){
147 show_custom_perms(true);
147 show_custom_perms(true);
148 }
148 }
149 else{
149 else{
150 show_custom_perms(false);
150 show_custom_perms(false);
151 }
151 }
152 })
152 })
153 })
153 })
154 </script>
154 </script>
155
155
156 </%def>
156 </%def>
@@ -1,268 +1,268 b''
1 ## snippet for displaying permissions overview for users
1 ## snippet for displaying permissions overview for users
2 ## usage:
2 ## usage:
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 ## ${p.perms_summary(c.perm_user.permissions)}
4 ## ${p.perms_summary(c.perm_user.permissions)}
5
5
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 <div id="perms" class="table fields">
7 <div id="perms" class="table fields">
8 %for section in sorted(permissions.keys()):
8 %for section in sorted(permissions.keys()):
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10 <div class="panel-heading">
10 <div class="panel-heading">
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 % if side_link:
12 % if side_link:
13 <div class="pull-right">
13 <div class="pull-right">
14 <a href="${side_link}">${_('in JSON format')}</a>
14 <a href="${side_link}">${_('in JSON format')}</a>
15 </div>
15 </div>
16 % endif
16 % endif
17 </div>
17 </div>
18 <div class="panel-body">
18 <div class="panel-body">
19 <div class="perms_section_head field">
19 <div class="perms_section_head field">
20 <div class="radios">
20 <div class="radios">
21 %if section != 'global':
21 %if section != 'global':
22 <span class="permissions_boxes">
22 <span class="permissions_boxes">
23 <span class="desc">${_('show')}: </span>
23 <span class="desc">${_('show')}: </span>
24 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
24 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
25 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
25 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
26 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
26 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
27 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
27 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
28 </span>
28 </span>
29 %endif
29 %endif
30 </div>
30 </div>
31 </div>
31 </div>
32 <div class="field">
32 <div class="field">
33 %if not permissions[section]:
33 %if not permissions[section]:
34 <p class="empty_data help-block">${_('No permissions defined')}</p>
34 <p class="empty_data help-block">${_('No permissions defined')}</p>
35 %else:
35 %else:
36 <div id='tbl_list_wrap_${section}'>
36 <div id='tbl_list_wrap_${section}'>
37 <table id="tbl_list_${section}" class="rctable">
37 <table id="tbl_list_${section}" class="rctable">
38 ## global permission box
38 ## global permission box
39 %if section == 'global':
39 %if section == 'global':
40 <thead>
40 <thead>
41 <tr>
41 <tr>
42 <th colspan="2" class="left">${_('Permission')}</th>
42 <th colspan="2" class="left">${_('Permission')}</th>
43 %if actions:
43 %if actions:
44 <th colspan="2">${_('Edit Permission')}</th>
44 <th colspan="2">${_('Edit Permission')}</th>
45 %endif
45 %endif
46 </thead>
46 </thead>
47 <tbody>
47 <tbody>
48
48
49 <%
49 <%
50 def get_section_perms(prefix, opts):
50 def get_section_perms(prefix, opts):
51 _selected = []
51 _selected = []
52 for op in opts:
52 for op in opts:
53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
54 _selected.append(op)
54 _selected.append(op)
55 admin = 'hg.admin' in opts
55 admin = 'hg.admin' in opts
56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
57 return admin, _selected_vals, _selected
57 return admin, _selected_vals, _selected
58 %>
58 %>
59
59
60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
61 <tr>
61 <tr>
62 <td class="td-tags">
62 <td class="td-tags">
63 ${lbl}
63 ${lbl}
64 </td>
64 </td>
65 <td class="td-tags">
65 <td class="td-tags">
66 %if val[0]:
66 %if val[0]:
67 %if not val_lbl:
67 %if not val_lbl:
68 ## super admin case
68 ## super admin case
69 True
69 True
70 %else:
70 %else:
71 <span class="perm_tag admin">${val_lbl}.admin</span>
71 <span class="perm_tag admin">${val_lbl}.admin</span>
72 %endif
72 %endif
73 %else:
73 %else:
74 %if not val_lbl:
74 %if not val_lbl:
75 ${
75 ${
76 {'false': False,
76 {'false': False,
77 'true': True,
77 'true': True,
78 'none': False,
78 'none': False,
79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
80 }
80 }
81 %else:
81 %else:
82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
83 %endif
83 %endif
84 %endif
84 %endif
85 </td>
85 </td>
86 %if actions:
86 %if actions:
87
87
88 % if edit_url or edit_global_url:
88 % if edit_url or edit_global_url:
89
89
90 <td class="td-action">
90 <td class="td-action">
91 % if edit_url:
91 % if edit_url:
92 <a href="${edit_url}">${_('edit')}</a>
92 <a href="${edit_url}">${_('edit')}</a>
93 % else:
93 % else:
94 -
94 -
95 % endif
95 % endif
96 </td>
96 </td>
97
97
98 <td class="td-action">
98 <td class="td-action">
99 % if edit_global_url:
99 % if edit_global_url:
100 <a href="${edit_global_url}">${_('edit global')}</a>
100 <a href="${edit_global_url}">${_('edit global')}</a>
101 % else:
101 % else:
102 -
102 -
103 % endif
103 % endif
104 </td>
104 </td>
105
105
106 % else:
106 % else:
107 <td class="td-action"></td>
107 <td class="td-action"></td>
108 <td class="td-action">
108 <td class="td-action">
109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
110 <td class="td-action">
110 <td class="td-action">
111 % endif
111 % endif
112
112
113 %endif
113 %endif
114 </tr>
114 </tr>
115 </%def>
115 </%def>
116
116
117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
119
119
120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
122
122
123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
125
125
126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
127 edit_url=h.url('edit_user', user_id=c.user.user_id, anchor='admin'), edit_global_url=None)}
127 edit_url=h.url('edit_user', user_id=c.user.user_id, anchor='admin'), edit_global_url=None)}
128
128
129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
130 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=None)}
130 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=None)}
131
131
132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
133 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
133 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
134
134
135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
136 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
136 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
137
137
138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
139 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
139 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
140
140
141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
142 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
142 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
143
143
144 </tbody>
144 </tbody>
145 %else:
145 %else:
146 ## none/read/write/admin permissions on groups/repos etc
146 ## none/read/write/admin permissions on groups/repos etc
147 <thead>
147 <thead>
148 <tr>
148 <tr>
149 <th>${_('Name')}</th>
149 <th>${_('Name')}</th>
150 <th>${_('Permission')}</th>
150 <th>${_('Permission')}</th>
151 %if actions:
151 %if actions:
152 <th>${_('Edit Permission')}</th>
152 <th>${_('Edit Permission')}</th>
153 %endif
153 %endif
154 </thead>
154 </thead>
155 <tbody class="section_${section}">
155 <tbody class="section_${section}">
156 <%
156 <%
157 def sorter(permissions):
157 def sorter(permissions):
158 def custom_sorter(item):
158 def custom_sorter(item):
159 ## read/write/admin
159 ## read/write/admin
160 section = item[1].split('.')[-1]
160 section = item[1].split('.')[-1]
161 section_importance = {'none': u'0',
161 section_importance = {'none': u'0',
162 'read': u'1',
162 'read': u'1',
163 'write':u'2',
163 'write':u'2',
164 'admin':u'3'}.get(section)
164 'admin':u'3'}.get(section)
165 ## sort by group importance+name
165 ## sort by group importance+name
166 return section_importance+item[0]
166 return section_importance+item[0]
167 return sorted(permissions, key=custom_sorter)
167 return sorted(permissions, key=custom_sorter)
168 %>
168 %>
169 %for k, section_perm in sorter(permissions[section].items()):
169 %for k, section_perm in sorter(permissions[section].items()):
170 %if section_perm.split('.')[-1] != 'none' or show_all:
170 %if section_perm.split('.')[-1] != 'none' or show_all:
171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
172 <td class="td-name">
172 <td class="td-name">
173 %if section == 'repositories':
173 %if section == 'repositories':
174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
175 %elif section == 'repositories_groups':
175 %elif section == 'repositories_groups':
176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
177 %elif section == 'user_groups':
177 %elif section == 'user_groups':
178 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
178 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
179 ${k}
179 ${k}
180 %endif
180 %endif
181 </td>
181 </td>
182 <td class="td-tags">
182 <td class="td-tags">
183 %if hasattr(permissions[section], 'perm_origin_stack'):
183 %if hasattr(permissions[section], 'perm_origin_stack'):
184 <div>
184 <div>
185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
186
186
187 % if i > 0:
187 % if i > 0:
188 <div style="color: #979797">
188 <div style="color: #979797">
189 <i class="icon-arrow_up"></i>
189 <i class="icon-arrow_up"></i>
190 ${_('overridden by')}
190 ${_('overridden by')}
191 <i class="icon-arrow_up"></i>
191 <i class="icon-arrow_up"></i>
192 </div>
192 </div>
193 % endif
193 % endif
194
194
195 <div>
195 <div>
196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
197 ${perm} (${origin})
197 ${perm} (${origin})
198 </span>
198 </span>
199 </div>
199 </div>
200
200
201 %endfor
201 %endfor
202 </div>
202 </div>
203 %else:
203 %else:
204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
205 %endif
205 %endif
206 </td>
206 </td>
207 %if actions:
207 %if actions:
208 <td class="td-action">
208 <td class="td-action">
209 %if section == 'repositories':
209 %if section == 'repositories':
210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
211 %elif section == 'repositories_groups':
211 %elif section == 'repositories_groups':
212 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
212 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
213 %elif section == 'user_groups':
213 %elif section == 'user_groups':
214 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
214 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
215 %endif
215 %endif
216 </td>
216 </td>
217 %endif
217 %endif
218 </tr>
218 </tr>
219 %endif
219 %endif
220 %endfor
220 %endfor
221
221
222 <tr id="empty_${section}" class="noborder" style="display:none;">
222 <tr id="empty_${section}" class="noborder" style="display:none;">
223 <td colspan="6">${_('No permission defined')}</td>
223 <td colspan="6">${_('No permission defined')}</td>
224 </tr>
224 </tr>
225
225
226 </tbody>
226 </tbody>
227 %endif
227 %endif
228 </table>
228 </table>
229 </div>
229 </div>
230 %endif
230 %endif
231 </div>
231 </div>
232 </div>
232 </div>
233 </div>
233 </div>
234 %endfor
234 %endfor
235 </div>
235 </div>
236
236
237 <script>
237 <script>
238 $(document).ready(function(){
238 $(document).ready(function(){
239 var show_empty = function(section){
239 var show_empty = function(section){
240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
241 if(visible == 0){
241 if(visible == 0){
242 $('#empty_{0}'.format(section)).show();
242 $('#empty_{0}'.format(section)).show();
243 }
243 }
244 else{
244 else{
245 $('#empty_{0}'.format(section)).hide();
245 $('#empty_{0}'.format(section)).hide();
246 }
246 }
247 };
247 };
248 $('.perm_filter').on('change', function(e){
248 $('.perm_filter').on('change', function(e){
249 var self = this;
249 var self = this;
250 var section = $(this).attr('section');
250 var section = $(this).attr('section');
251
251
252 var opts = {};
252 var opts = {};
253 var elems = $('.filter_' + section).each(function(el){
253 var elems = $('.filter_' + section).each(function(el){
254 var perm_type = $(this).attr('perm_type');
254 var perm_type = $(this).attr('perm_type');
255 var checked = this.checked;
255 var checked = this.checked;
256 opts[perm_type] = checked;
256 opts[perm_type] = checked;
257 if(checked){
257 if(checked){
258 $('.'+section+'_'+perm_type).show();
258 $('.'+section+'_'+perm_type).show();
259 }
259 }
260 else{
260 else{
261 $('.'+section+'_'+perm_type).hide();
261 $('.'+section+'_'+perm_type).hide();
262 }
262 }
263 });
263 });
264 show_empty(section);
264 show_empty(section);
265 })
265 })
266 })
266 })
267 </script>
267 </script>
268 </%def>
268 </%def>
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now