##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 33 from rhodecode.model.db import User
33 34 from rhodecode.model.scm import ScmModel
34 35
35 36 log = logging.getLogger(__name__)
36 37
37 38
38 39 ADMIN_PREFIX = '/_admin'
39 40 STATIC_FILE_PREFIX = '/_static'
40 41
41 42 URL_NAME_REQUIREMENTS = {
42 43 # group name can have a slash in them, but they must not end with a slash
43 44 'group_name': r'.*?[^/]',
44 45 'repo_group_name': r'.*?[^/]',
45 46 # repo names can have a slash in them, but they must not end with a slash
46 47 'repo_name': r'.*?[^/]',
47 48 # file path eats up everything at the end
48 49 'f_path': r'.*',
49 50 # reference types
50 51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 53 }
53 54
54 55
55 56 def add_route_with_slash(config,name, pattern, **kw):
56 57 config.add_route(name, pattern, **kw)
57 58 if not pattern.endswith('/'):
58 59 config.add_route(name + '_slash', pattern + '/', **kw)
59 60
60 61
61 62 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
62 63 """
63 64 Adds regex requirements to pyramid routes using a mapping dict
64 65 e.g::
65 66 add_route_requirements('{repo_name}/settings')
66 67 """
67 68 for key, regex in requirements.items():
68 69 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
69 70 return route_path
70 71
71 72
72 73 def get_format_ref_id(repo):
73 74 """Returns a `repo` specific reference formatter function"""
74 75 if h.is_svn(repo):
75 76 return _format_ref_id_svn
76 77 else:
77 78 return _format_ref_id
78 79
79 80
80 81 def _format_ref_id(name, raw_id):
81 82 """Default formatting of a given reference `name`"""
82 83 return name
83 84
84 85
85 86 def _format_ref_id_svn(name, raw_id):
86 87 """Special way of formatting a reference for Subversion including path"""
87 88 return '%s@%s' % (name, raw_id)
88 89
89 90
90 91 class TemplateArgs(StrictAttributeDict):
91 92 pass
92 93
93 94
94 95 class BaseAppView(object):
95 96
96 97 def __init__(self, context, request):
97 98 self.request = request
98 99 self.context = context
99 100 self.session = request.session
100 101 self._rhodecode_user = request.user # auth user
101 102 self._rhodecode_db_user = self._rhodecode_user.get_instance()
102 103 self._maybe_needs_password_change(
103 104 request.matched_route.name, self._rhodecode_db_user)
104 105
105 106 def _maybe_needs_password_change(self, view_name, user_obj):
106 107 log.debug('Checking if user %s needs password change on view %s',
107 108 user_obj, view_name)
108 109 skip_user_views = [
109 110 'logout', 'login',
110 111 'my_account_password', 'my_account_password_update'
111 112 ]
112 113
113 114 if not user_obj:
114 115 return
115 116
116 117 if user_obj.username == User.DEFAULT_USER:
117 118 return
118 119
119 120 now = time.time()
120 121 should_change = user_obj.user_data.get('force_password_change')
121 122 change_after = safe_int(should_change) or 0
122 123 if should_change and now > change_after:
123 124 log.debug('User %s requires password change', user_obj)
124 125 h.flash('You are required to change your password', 'warning',
125 126 ignore_duplicate=True)
126 127
127 128 if view_name not in skip_user_views:
128 129 raise HTTPFound(
129 130 self.request.route_path('my_account_password'))
130 131
131 132 def _log_creation_exception(self, e, repo_name):
132 133 _ = self.request.translate
133 134 reason = None
134 135 if len(e.args) == 2:
135 136 reason = e.args[1]
136 137
137 138 if reason == 'INVALID_CERTIFICATE':
138 139 log.exception(
139 140 'Exception creating a repository: invalid certificate')
140 141 msg = (_('Error creating repository %s: invalid certificate')
141 142 % repo_name)
142 143 else:
143 144 log.exception("Exception creating a repository")
144 145 msg = (_('Error creating repository %s')
145 146 % repo_name)
146 147 return msg
147 148
148 149 def _get_local_tmpl_context(self, include_app_defaults=False):
149 150 c = TemplateArgs()
150 151 c.auth_user = self.request.user
151 152 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
152 153 c.rhodecode_user = self.request.user
153 154
154 155 if include_app_defaults:
155 156 # NOTE(marcink): after full pyramid migration include_app_defaults
156 157 # should be turned on by default
157 158 from rhodecode.lib.base import attach_context_attributes
158 159 attach_context_attributes(c, self.request, self.request.user.user_id)
159 160
160 161 return c
161 162
162 163 def _register_global_c(self, tmpl_args):
163 164 """
164 165 Registers attributes to pylons global `c`
165 166 """
166 167
167 168 # TODO(marcink): remove once pyramid migration is finished
168 169 from pylons import tmpl_context as c
169 170 try:
170 171 for k, v in tmpl_args.items():
171 172 setattr(c, k, v)
172 173 except TypeError:
173 174 log.exception('Failed to register pylons C')
174 175 pass
175 176
176 177 def _get_template_context(self, tmpl_args):
177 178 self._register_global_c(tmpl_args)
178 179
179 180 local_tmpl_args = {
180 181 'defaults': {},
181 182 'errors': {},
182 183 # register a fake 'c' to be used in templates instead of global
183 184 # pylons c, after migration to pyramid we should rename it to 'c'
184 185 # make sure we replace usage of _c in templates too
185 186 '_c': tmpl_args
186 187 }
187 188 local_tmpl_args.update(tmpl_args)
188 189 return local_tmpl_args
189 190
190 191 def load_default_context(self):
191 192 """
192 193 example:
193 194
194 195 def load_default_context(self):
195 196 c = self._get_local_tmpl_context()
196 197 c.custom_var = 'foobar'
197 198 self._register_global_c(c)
198 199 return c
199 200 """
200 201 raise NotImplementedError('Needs implementation in view class')
201 202
202 203
203 204 class RepoAppView(BaseAppView):
204 205
205 206 def __init__(self, context, request):
206 207 super(RepoAppView, self).__init__(context, request)
207 208 self.db_repo = request.db_repo
208 209 self.db_repo_name = self.db_repo.repo_name
209 210 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
210 211
211 212 def _handle_missing_requirements(self, error):
212 213 log.error(
213 214 'Requirements are missing for repository %s: %s',
214 215 self.db_repo_name, error.message)
215 216
216 217 def _get_local_tmpl_context(self, include_app_defaults=False):
217 218 _ = self.request.translate
218 219 c = super(RepoAppView, self)._get_local_tmpl_context(
219 220 include_app_defaults=include_app_defaults)
220 221
221 222 # register common vars for this type of view
222 223 c.rhodecode_db_repo = self.db_repo
223 224 c.repo_name = self.db_repo_name
224 225 c.repository_pull_requests = self.db_repo_pull_requests
225 226
226 227 c.repository_requirements_missing = False
227 228 try:
228 229 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
229 230 except RepositoryRequirementError as e:
230 231 c.repository_requirements_missing = True
231 232 self._handle_missing_requirements(e)
232 233 self.rhodecode_vcs_repo = None
233 234
234 235 if (not c.repository_requirements_missing
235 236 and self.rhodecode_vcs_repo is None):
236 237 # unable to fetch this repo as vcs instance, report back to user
237 238 h.flash(_(
238 239 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
239 240 "Please check if it exist, or is not damaged.") %
240 241 {'repo_name': c.repo_name},
241 242 category='error', ignore_duplicate=True)
242 243 raise HTTPFound(h.route_path('home'))
243 244
244 245 return c
245 246
246 247 def _get_f_path(self, matchdict, default=None):
247 248 f_path = matchdict.get('f_path')
248 249 if f_path:
249 250 # fix for multiple initial slashes that causes errors for GIT
250 251 return f_path.lstrip('/')
251 252
252 253 return default
253 254
254 255
255 256 class RepoGroupAppView(BaseAppView):
256 257 def __init__(self, context, request):
257 258 super(RepoGroupAppView, self).__init__(context, request)
258 259 self.db_repo_group = request.db_repo_group
259 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 270 class DataGridAppView(object):
263 271 """
264 272 Common class to have re-usable grid rendering components
265 273 """
266 274
267 275 def _extract_ordering(self, request, column_map=None):
268 276 column_map = column_map or {}
269 277 column_index = safe_int(request.GET.get('order[0][column]'))
270 278 order_dir = request.GET.get(
271 279 'order[0][dir]', 'desc')
272 280 order_by = request.GET.get(
273 281 'columns[%s][data][sort]' % column_index, 'name_raw')
274 282
275 283 # translate datatable to DB columns
276 284 order_by = column_map.get(order_by) or order_by
277 285
278 286 search_q = request.GET.get('search[value]')
279 287 return search_q, order_by, order_dir
280 288
281 289 def _extract_chunk(self, request):
282 290 start = safe_int(request.GET.get('start'), 0)
283 291 length = safe_int(request.GET.get('length'), 25)
284 292 draw = safe_int(request.GET.get('draw'))
285 293 return draw, start, length
286 294
287 295 def _get_order_col(self, order_by, model):
288 296 if isinstance(order_by, basestring):
289 297 try:
290 298 return operator.attrgetter(order_by)(model)
291 299 except AttributeError:
292 300 return None
293 301 else:
294 302 return order_by
295 303
296 304
297 305 class BaseReferencesView(RepoAppView):
298 306 """
299 307 Base for reference view for branches, tags and bookmarks.
300 308 """
301 309 def load_default_context(self):
302 310 c = self._get_local_tmpl_context()
303 311
304 312 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
305 313 c.repo_info = self.db_repo
306 314
307 315 self._register_global_c(c)
308 316 return c
309 317
310 318 def load_refs_context(self, ref_items, partials_template):
311 319 _render = self.request.get_partial_renderer(partials_template)
312 320 pre_load = ["author", "date", "message"]
313 321
314 322 is_svn = h.is_svn(self.rhodecode_vcs_repo)
315 323 is_hg = h.is_hg(self.rhodecode_vcs_repo)
316 324
317 325 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
318 326
319 327 closed_refs = {}
320 328 if is_hg:
321 329 closed_refs = self.rhodecode_vcs_repo.branches_closed
322 330
323 331 data = []
324 332 for ref_name, commit_id in ref_items:
325 333 commit = self.rhodecode_vcs_repo.get_commit(
326 334 commit_id=commit_id, pre_load=pre_load)
327 335 closed = ref_name in closed_refs
328 336
329 337 # TODO: johbo: Unify generation of reference links
330 338 use_commit_id = '/' in ref_name or is_svn
331 339
332 340 if use_commit_id:
333 341 files_url = h.route_path(
334 342 'repo_files',
335 343 repo_name=self.db_repo_name,
336 344 f_path=ref_name if is_svn else '',
337 345 commit_id=commit_id)
338 346
339 347 else:
340 348 files_url = h.route_path(
341 349 'repo_files',
342 350 repo_name=self.db_repo_name,
343 351 f_path=ref_name if is_svn else '',
344 352 commit_id=ref_name,
345 353 _query=dict(at=ref_name))
346 354
347 355 data.append({
348 356 "name": _render('name', ref_name, files_url, closed),
349 357 "name_raw": ref_name,
350 358 "date": _render('date', commit.date),
351 359 "date_raw": datetime_to_time(commit.date),
352 360 "author": _render('author', commit.author),
353 361 "commit": _render(
354 362 'commit', commit.message, commit.raw_id, commit.idx),
355 363 "commit_raw": commit.idx,
356 364 "compare": _render(
357 365 'compare', format_ref_id(ref_name, commit.raw_id)),
358 366 })
359 367
360 368 return data
361 369
362 370
363 371 class RepoRoutePredicate(object):
364 372 def __init__(self, val, config):
365 373 self.val = val
366 374
367 375 def text(self):
368 376 return 'repo_route = %s' % self.val
369 377
370 378 phash = text
371 379
372 380 def __call__(self, info, request):
373 381
374 382 if hasattr(request, 'vcs_call'):
375 383 # skip vcs calls
376 384 return
377 385
378 386 repo_name = info['match']['repo_name']
379 387 repo_model = repo.RepoModel()
380 388 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
381 389
382 390 def redirect_if_creating(db_repo):
383 391 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
384 392 raise HTTPFound(
385 393 request.route_path('repo_creating',
386 394 repo_name=db_repo.repo_name))
387 395
388 396 if by_name_match:
389 397 # register this as request object we can re-use later
390 398 request.db_repo = by_name_match
391 399 redirect_if_creating(by_name_match)
392 400 return True
393 401
394 402 by_id_match = repo_model.get_repo_by_id(repo_name)
395 403 if by_id_match:
396 404 request.db_repo = by_id_match
397 405 redirect_if_creating(by_id_match)
398 406 return True
399 407
400 408 return False
401 409
402 410
403 411 class RepoTypeRoutePredicate(object):
404 412 def __init__(self, val, config):
405 413 self.val = val or ['hg', 'git', 'svn']
406 414
407 415 def text(self):
408 416 return 'repo_accepted_type = %s' % self.val
409 417
410 418 phash = text
411 419
412 420 def __call__(self, info, request):
413 421 if hasattr(request, 'vcs_call'):
414 422 # skip vcs calls
415 423 return
416 424
417 425 rhodecode_db_repo = request.db_repo
418 426
419 427 log.debug(
420 428 '%s checking repo type for %s in %s',
421 429 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
422 430
423 431 if rhodecode_db_repo.repo_type in self.val:
424 432 return True
425 433 else:
426 434 log.warning('Current view is not supported for repo type:%s',
427 435 rhodecode_db_repo.repo_type)
428 436 #
429 437 # h.flash(h.literal(
430 438 # _('Action not supported for %s.' % rhodecode_repo.alias)),
431 439 # category='warning')
432 440 # return redirect(
433 441 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
434 442
435 443 return False
436 444
437 445
438 446 class RepoGroupRoutePredicate(object):
439 447 def __init__(self, val, config):
440 448 self.val = val
441 449
442 450 def text(self):
443 451 return 'repo_group_route = %s' % self.val
444 452
445 453 phash = text
446 454
447 455 def __call__(self, info, request):
448 456 if hasattr(request, 'vcs_call'):
449 457 # skip vcs calls
450 458 return
451 459
452 460 repo_group_name = info['match']['repo_group_name']
453 461 repo_group_model = repo_group.RepoGroupModel()
454 462 by_name_match = repo_group_model.get_by_group_name(
455 463 repo_group_name, cache=True)
456 464
457 465 if by_name_match:
458 466 # register this as request object we can re-use later
459 467 request.db_repo_group = by_name_match
460 468 return True
461 469
462 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 500 def includeme(config):
466 501 config.add_route_predicate(
467 502 'repo_route', RepoRoutePredicate)
468 503 config.add_route_predicate(
469 504 'repo_accepted_types', RepoTypeRoutePredicate)
470 505 config.add_route_predicate(
471 506 'repo_group_route', RepoGroupRoutePredicate)
507 config.add_route_predicate(
508 'user_group_route', UserGroupRoutePredicate)
@@ -1,245 +1,241 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 Admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 33 name='admin_audit_logs',
34 34 pattern='/audit_logs')
35 35
36 36 config.add_route(
37 37 name='pull_requests_global_0', # backward compat
38 38 pattern='/pull_requests/{pull_request_id:\d+}')
39 39 config.add_route(
40 40 name='pull_requests_global_1', # backward compat
41 41 pattern='/pull-requests/{pull_request_id:\d+}')
42 42 config.add_route(
43 43 name='pull_requests_global',
44 44 pattern='/pull-request/{pull_request_id:\d+}')
45 45
46 46 config.add_route(
47 47 name='admin_settings_open_source',
48 48 pattern='/settings/open_source')
49 49 config.add_route(
50 50 name='admin_settings_vcs_svn_generate_cfg',
51 51 pattern='/settings/vcs/svn_generate_cfg')
52 52
53 53 config.add_route(
54 54 name='admin_settings_system',
55 55 pattern='/settings/system')
56 56 config.add_route(
57 57 name='admin_settings_system_update',
58 58 pattern='/settings/system/updates')
59 59
60 60 config.add_route(
61 61 name='admin_settings_sessions',
62 62 pattern='/settings/sessions')
63 63 config.add_route(
64 64 name='admin_settings_sessions_cleanup',
65 65 pattern='/settings/sessions/cleanup')
66 66
67 67 config.add_route(
68 68 name='admin_settings_process_management',
69 69 pattern='/settings/process_management')
70 70 config.add_route(
71 71 name='admin_settings_process_management_signal',
72 72 pattern='/settings/process_management/signal')
73 73
74 74 # global permissions
75 75
76 76 config.add_route(
77 77 name='admin_permissions_application',
78 78 pattern='/permissions/application')
79 79 config.add_route(
80 80 name='admin_permissions_application_update',
81 81 pattern='/permissions/application/update')
82 82
83 83 config.add_route(
84 84 name='admin_permissions_global',
85 85 pattern='/permissions/global')
86 86 config.add_route(
87 87 name='admin_permissions_global_update',
88 88 pattern='/permissions/global/update')
89 89
90 90 config.add_route(
91 91 name='admin_permissions_object',
92 92 pattern='/permissions/object')
93 93 config.add_route(
94 94 name='admin_permissions_object_update',
95 95 pattern='/permissions/object/update')
96 96
97 97 config.add_route(
98 98 name='admin_permissions_ips',
99 99 pattern='/permissions/ips')
100 100
101 101 config.add_route(
102 102 name='admin_permissions_overview',
103 103 pattern='/permissions/overview')
104 104
105 105 config.add_route(
106 106 name='admin_permissions_auth_token_access',
107 107 pattern='/permissions/auth_token_access')
108 108
109 109 config.add_route(
110 110 name='admin_permissions_ssh_keys',
111 111 pattern='/permissions/ssh_keys')
112 112 config.add_route(
113 113 name='admin_permissions_ssh_keys_data',
114 114 pattern='/permissions/ssh_keys/data')
115 115 config.add_route(
116 116 name='admin_permissions_ssh_keys_update',
117 117 pattern='/permissions/ssh_keys/update')
118 118
119 119 # users admin
120 120 config.add_route(
121 121 name='users',
122 122 pattern='/users')
123 123
124 124 config.add_route(
125 125 name='users_data',
126 126 pattern='/users_data')
127 127
128 128 # user auth tokens
129 129 config.add_route(
130 130 name='edit_user_auth_tokens',
131 131 pattern='/users/{user_id:\d+}/edit/auth_tokens')
132 132 config.add_route(
133 133 name='edit_user_auth_tokens_add',
134 134 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
135 135 config.add_route(
136 136 name='edit_user_auth_tokens_delete',
137 137 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
138 138
139 139 # user ssh keys
140 140 config.add_route(
141 141 name='edit_user_ssh_keys',
142 142 pattern='/users/{user_id:\d+}/edit/ssh_keys')
143 143 config.add_route(
144 144 name='edit_user_ssh_keys_generate_keypair',
145 145 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
146 146 config.add_route(
147 147 name='edit_user_ssh_keys_add',
148 148 pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
149 149 config.add_route(
150 150 name='edit_user_ssh_keys_delete',
151 151 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
152 152
153 153 # user emails
154 154 config.add_route(
155 155 name='edit_user_emails',
156 156 pattern='/users/{user_id:\d+}/edit/emails')
157 157 config.add_route(
158 158 name='edit_user_emails_add',
159 159 pattern='/users/{user_id:\d+}/edit/emails/new')
160 160 config.add_route(
161 161 name='edit_user_emails_delete',
162 162 pattern='/users/{user_id:\d+}/edit/emails/delete')
163 163
164 164 # user IPs
165 165 config.add_route(
166 166 name='edit_user_ips',
167 167 pattern='/users/{user_id:\d+}/edit/ips')
168 168 config.add_route(
169 169 name='edit_user_ips_add',
170 170 pattern='/users/{user_id:\d+}/edit/ips/new')
171 171 config.add_route(
172 172 name='edit_user_ips_delete',
173 173 pattern='/users/{user_id:\d+}/edit/ips/delete')
174 174
175 175 # user perms
176 176 config.add_route(
177 177 name='edit_user_perms_summary',
178 178 pattern='/users/{user_id:\d+}/edit/permissions_summary')
179 179 config.add_route(
180 180 name='edit_user_perms_summary_json',
181 181 pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
182 182
183 # user groups management
183 # user user groups management
184 184 config.add_route(
185 185 name='edit_user_groups_management',
186 186 pattern='/users/{user_id:\d+}/edit/groups_management')
187 187
188 188 config.add_route(
189 189 name='edit_user_groups_management_updates',
190 190 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
191 191
192 192 # user audit logs
193 193 config.add_route(
194 194 name='edit_user_audit_logs',
195 195 pattern='/users/{user_id:\d+}/edit/audit')
196 196
197 # user groups admin
197 # user-groups admin
198 198 config.add_route(
199 199 name='user_groups',
200 200 pattern='/user_groups')
201 201
202 202 config.add_route(
203 203 name='user_groups_data',
204 204 pattern='/user_groups_data')
205 205
206 206 config.add_route(
207 name='user_group_members_data',
208 pattern='/user_groups/{user_group_id:\d+}/members')
207 name='user_groups_new',
208 pattern='/user_groups/new')
209 209
210 # user groups perms
211 210 config.add_route(
212 name='edit_user_group_perms_summary',
213 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary')
214 config.add_route(
215 name='edit_user_group_perms_summary_json',
216 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json')
211 name='user_groups_create',
212 pattern='/user_groups/create')
217 213
218 214 # repos admin
219 215 config.add_route(
220 216 name='repos',
221 217 pattern='/repos')
222 218
223 219 config.add_route(
224 220 name='repo_new',
225 221 pattern='/repos/new')
226 222
227 223 config.add_route(
228 224 name='repo_create',
229 225 pattern='/repos/create')
230 226
231 227
232 228 def includeme(config):
233 229 settings = config.get_settings()
234 230
235 231 # Create admin navigation registry and add it to the pyramid registry.
236 232 labs_active = str2bool(settings.get('labs_settings_active', False))
237 233 navigation_registry = NavigationRegistry(labs_active=labs_active)
238 234 config.registry.registerUtility(navigation_registry)
239 235
240 236 # main admin routes
241 237 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
242 238 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
243 239
244 240 # Scan module for configuration decorators.
245 241 config.scan('.views', ignore='.tests')
@@ -1,113 +1,170 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import UserGroup, User
24 24 from rhodecode.model.meta import Session
25 25
26 26 from rhodecode.tests import (
27 27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 28 from rhodecode.tests.fixture import Fixture
29 29
30 30 fixture = Fixture()
31 31
32 32
33 33 def route_path(name, params=None, **kwargs):
34 34 import urllib
35 35 from rhodecode.apps._base import ADMIN_PREFIX
36 36
37 37 base_url = {
38 38 'user_groups': ADMIN_PREFIX + '/user_groups',
39 39 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
40 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 44 }[name].format(**kwargs)
42 45
43 46 if params:
44 47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 48 return base_url
46 49
47 50
48 51 class TestAdminUserGroupsView(TestController):
49 52
50 53 def test_show_users(self):
51 54 self.log_user()
52 55 self.app.get(route_path('user_groups'))
53 56
54 57 def test_show_user_groups_data(self, xhr_header):
55 58 self.log_user()
56 59 response = self.app.get(route_path(
57 60 'user_groups_data'), extra_environ=xhr_header)
58 61
59 62 all_user_groups = UserGroup.query().count()
60 63 assert response.json['recordsTotal'] == all_user_groups
61 64
62 65 def test_show_user_groups_data_filtered(self, xhr_header):
63 66 self.log_user()
64 67 response = self.app.get(route_path(
65 68 'user_groups_data', params={'search[value]': 'empty_search'}),
66 69 extra_environ=xhr_header)
67 70
68 71 all_user_groups = UserGroup.query().count()
69 72 assert response.json['recordsTotal'] == all_user_groups
70 73 assert response.json['recordsFiltered'] == 0
71 74
72 75 def test_usergroup_escape(self, user_util, xhr_header):
73 76 self.log_user()
74 77
75 78 xss_img = '<img src="/image1" onload="alert(\'Hello, World!\');">'
76 79 user = user_util.create_user()
77 80 user.name = xss_img
78 81 user.lastname = xss_img
79 82 Session().add(user)
80 83 Session().commit()
81 84
82 85 user_group = user_util.create_user_group()
83 86
84 87 user_group.users_group_name = xss_img
85 88 user_group.user_group_description = '<strong onload="alert();">DESC</strong>'
86 89
87 90 response = self.app.get(
88 91 route_path('user_groups_data'), extra_environ=xhr_header)
89 92
90 93 response.mustcontain(
91 94 '&lt;strong onload=&#34;alert();&#34;&gt;DESC&lt;/strong&gt;')
92 95 response.mustcontain(
93 96 '&lt;img src=&#34;/image1&#34; onload=&#34;'
94 97 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
95 98
96 99 def test_edit_user_group_autocomplete_empty_members(self, xhr_header, user_util):
97 100 self.log_user()
98 101 ug = user_util.create_user_group()
99 102 response = self.app.get(
100 103 route_path('user_group_members_data', user_group_id=ug.users_group_id),
101 104 extra_environ=xhr_header)
102 105
103 106 assert response.json == {'members': []}
104 107
105 108 def test_edit_user_group_autocomplete_members(self, xhr_header, user_util):
106 109 self.log_user()
107 110 members = [u.user_id for u in User.get_all()]
108 111 ug = user_util.create_user_group(members=members)
109 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 115 extra_environ=xhr_header)
112 116
113 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 import formencode
24 import formencode.htmlfill
25
23 26 from pyramid.httpexceptions import HTTPFound
24 27 from pyramid.view import view_config
25
26 from rhodecode.model.scm import UserGroupList
28 from pyramid.response import Response
29 from pyramid.renderers import render
27 30
28 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 32 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous,
31 HasUserGroupPermissionAnyDecorator)
32 from rhodecode.lib import helpers as h
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 from rhodecode.lib import helpers as h, audit_logger
33 35 from rhodecode.lib.utils import PartialRenderer
34 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 41 from rhodecode.model.db import (
36 joinedload, or_, count, User, UserGroup, UserGroupMember,
37 UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
42 or_, count, User, UserGroup, UserGroupMember)
38 43 from rhodecode.model.meta import Session
44 from rhodecode.model.user_group import UserGroupModel
39 45
40 46 log = logging.getLogger(__name__)
41 47
42 48
43 49 class AdminUserGroupsView(BaseAppView, DataGridAppView):
44 50
45 51 def load_default_context(self):
46 52 c = self._get_local_tmpl_context()
53
54 PermissionModel().set_global_permission_choices(
55 c, gettext_translator=self.request.translate)
56
47 57 self._register_global_c(c)
48 58 return c
49 59
50 60 # permission check in data loading of
51 61 # `user_groups_list_data` via UserGroupList
52 62 @LoginRequired()
53 63 @NotAnonymous()
54 64 @view_config(
55 65 route_name='user_groups', request_method='GET',
56 66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
57 67 def user_groups_list(self):
58 68 c = self.load_default_context()
59 69 return self._get_template_context(c)
60 70
61 71 # permission check inside
62 72 @LoginRequired()
63 73 @NotAnonymous()
64 74 @view_config(
65 75 route_name='user_groups_data', request_method='GET',
66 76 renderer='json_ext', xhr=True)
67 77 def user_groups_list_data(self):
68 78 column_map = {
69 79 'active': 'users_group_active',
70 80 'description': 'user_group_description',
71 81 'members': 'members_total',
72 82 'owner': 'user_username',
73 83 'sync': 'group_data'
74 84 }
75 85 draw, start, limit = self._extract_chunk(self.request)
76 86 search_q, order_by, order_dir = self._extract_ordering(
77 87 self.request, column_map=column_map)
78 88
79 89 _render = PartialRenderer('data_table/_dt_elements.mako')
80 90
81 91 def user_group_name(user_group_id, user_group_name):
82 92 return _render("user_group_name", user_group_id, user_group_name)
83 93
84 94 def user_group_actions(user_group_id, user_group_name):
85 95 return _render("user_group_actions", user_group_id, user_group_name)
86 96
87 97 def user_profile(username):
88 98 return _render('user_profile', username)
89 99
90 100 auth_user_group_list = UserGroupList(
91 101 UserGroup.query().all(), perm_set=['usergroup.admin'])
92 102
93 103 allowed_ids = []
94 104 for user_group in auth_user_group_list:
95 105 allowed_ids.append(user_group.users_group_id)
96 106
97 107 user_groups_data_total_count = UserGroup.query()\
98 108 .filter(UserGroup.users_group_id.in_(allowed_ids))\
99 109 .count()
100 110
101 111 member_count = count(UserGroupMember.user_id)
102 112 base_q = Session.query(
103 113 UserGroup.users_group_name,
104 114 UserGroup.user_group_description,
105 115 UserGroup.users_group_active,
106 116 UserGroup.users_group_id,
107 117 UserGroup.group_data,
108 118 User,
109 119 member_count.label('member_count')
110 120 ) \
111 121 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
112 122 .outerjoin(UserGroupMember) \
113 123 .join(User, User.user_id == UserGroup.user_id) \
114 124 .group_by(UserGroup, User)
115 125
116 126 if search_q:
117 127 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 128 base_q = base_q.filter(or_(
119 129 UserGroup.users_group_name.ilike(like_expression),
120 130 ))
121 131
122 132 user_groups_data_total_filtered_count = base_q.count()
123 133
124 134 if order_by == 'members_total':
125 135 sort_col = member_count
126 136 elif order_by == 'user_username':
127 137 sort_col = User.username
128 138 else:
129 139 sort_col = getattr(UserGroup, order_by, None)
130 140
131 141 if isinstance(sort_col, count) or sort_col:
132 142 if order_dir == 'asc':
133 143 sort_col = sort_col.asc()
134 144 else:
135 145 sort_col = sort_col.desc()
136 146
137 147 base_q = base_q.order_by(sort_col)
138 148 base_q = base_q.offset(start).limit(limit)
139 149
140 150 # authenticated access to user groups
141 151 auth_user_group_list = base_q.all()
142 152
143 153 user_groups_data = []
144 154 for user_gr in auth_user_group_list:
145 155 user_groups_data.append({
146 156 "users_group_name": user_group_name(
147 157 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
148 158 "name_raw": h.escape(user_gr.users_group_name),
149 159 "description": h.escape(user_gr.user_group_description),
150 160 "members": user_gr.member_count,
151 161 # NOTE(marcink): because of advanced query we
152 162 # need to load it like that
153 163 "sync": UserGroup._load_group_data(
154 164 user_gr.group_data).get('extern_type'),
155 165 "active": h.bool2icon(user_gr.users_group_active),
156 166 "owner": user_profile(user_gr.User.username),
157 167 "action": user_group_actions(
158 168 user_gr.users_group_id, user_gr.users_group_name)
159 169 })
160 170
161 171 data = ({
162 172 'draw': draw,
163 173 'data': user_groups_data,
164 174 'recordsTotal': user_groups_data_total_count,
165 175 'recordsFiltered': user_groups_data_total_filtered_count,
166 176 })
167 177
168 178 return data
169 179
170 180 @LoginRequired()
171 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
181 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
172 182 @view_config(
173 route_name='user_group_members_data', request_method='GET',
174 renderer='json_ext', xhr=True)
175 def user_group_members(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):
183 route_name='user_groups_new', request_method='GET',
184 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
185 def user_groups_new(self):
235 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 187 return self._get_template_context(c)
244 188
245 189 @LoginRequired()
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
190 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
191 @CSRFRequired()
247 192 @view_config(
248 route_name='edit_user_group_perms_summary_json', request_method='GET',
249 renderer='json_ext')
250 def user_group_perms_summary_json(self):
251 self.load_default_context()
193 route_name='user_groups_create', request_method='POST',
194 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
195 def user_groups_create(self):
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')
254 user_group = UserGroup.get_or_404(user_group_id)
216 user_group_link = h.link_to(
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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
25 from rhodecode.model.db import User, UserGroup
24 TestController, assert_session_flash, TEST_USER_ADMIN_LOGIN)
25 from rhodecode.model.db import UserGroup
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.tests.fixture import Fixture
28 28
29 TEST_USER_GROUP = 'admins_test'
30
31 29 fixture = Fixture()
32 30
33 31
34 class TestAdminUsersGroupsController(TestController):
35
36 def test_create(self):
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})
32 def route_path(name, params=None, **kwargs):
33 import urllib
34 from rhodecode.apps._base import ADMIN_PREFIX
44 35
45 user_group_link = link_to(
46 users_group_name,
47 url('edit_users_group',
48 user_group_id=UserGroup.get_by_group_name(
49 users_group_name).users_group_id))
50 assert_session_flash(
51 response,
52 'Created user group %s' % user_group_link)
36 base_url = {
37 'user_groups': ADMIN_PREFIX + '/user_groups',
38 'user_groups_data': ADMIN_PREFIX + '/user_groups_data',
39 'user_group_members_data': ADMIN_PREFIX + '/user_groups/{user_group_id}/members',
40 'user_groups_new': ADMIN_PREFIX + '/user_groups/new',
41 'user_groups_create': ADMIN_PREFIX + '/user_groups/create',
42 'edit_user_group': ADMIN_PREFIX + '/user_groups/{user_group_id}/edit',
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 58 self.log_user()
56 users_group_name = TEST_USER_GROUP + 'sync'
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})
59 user_group_name = user_util.create_user_group().users_group_name
62 60
63 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 64 assert group.group_data.get('extern_type') is None
67 65
68 66 # enable
69 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 70 params={'csrf_token': self.csrf_token}, status=302)
72 71
73 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 74 assert group.group_data.get('extern_type') == 'manual'
76 75 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
77 76
78 77 # disable
79 78 self.app.post(
80 url('edit_user_group_advanced_sync',
79 route_path('edit_user_group_advanced_sync',
81 80 user_group_id=group.users_group_id),
82 81 params={'csrf_token': self.csrf_token}, status=302)
83 82
84 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 85 assert group.group_data.get('extern_type') is None
87 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 89 self.log_user()
91 users_group_name = TEST_USER_GROUP + 'another'
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)
90 user_group_id = user_util.create_user_group().users_group_id
106 91
107 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 95 self.app.post(
111 url('delete_users_group', user_group_id=group.users_group_id),
112 params={'_method': 'delete', 'csrf_token': self.csrf_token})
96 route_path('user_groups_delete', user_group_id=group.users_group_id),
97 params={'csrf_token': self.csrf_token})
113 98
114 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 102 assert group is None
118 103
119 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 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 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 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 108 ('', '', '', '', '', '', True, False),
124 109 ])
125 def test_global_perms_on_group(
110 def test_global_permissions_on_user_group(
126 111 self, repo_create, repo_create_write, user_group_create,
127 112 repo_group_create, fork_create, expect_error, expect_form_error,
128 inherit_default_permissions):
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})
113 inherit_default_permissions, user_util):
136 114
137 ug = UserGroup.get_by_group_name(users_group_name)
138 user_group_link = link_to(
139 users_group_name,
140 url('edit_users_group', user_group_id=ug.users_group_id))
141 assert_session_flash(
142 response,
143 'Created user group %s' % user_group_link)
144 response.follow()
115 self.log_user()
116 user_group = user_util.create_user_group()
117
118 user_group_name = user_group.users_group_name
119 user_group_id = user_group.users_group_id
145 120
146 121 # ENABLE REPO CREATE ON A GROUP
147 122 perm_params = {
148 123 'inherit_default_permissions': False,
149 124 'default_repo_create': repo_create,
150 125 'default_repo_create_on_write': repo_create_write,
151 126 'default_user_group_create': user_group_create,
152 127 'default_repo_group_create': repo_group_create,
153 128 'default_fork_create': fork_create,
154 129 'default_inherit_default_permissions': inherit_default_permissions,
155 130
156 '_method': 'put',
157 131 'csrf_token': self.csrf_token,
158 132 }
159 133 response = self.app.post(
160 url('edit_user_group_global_perms',
161 user_group_id=ug.users_group_id),
134 route_path('edit_user_group_global_perms_update',
135 user_group_id=user_group_id),
162 136 params=perm_params)
163 137
164 138 if expect_form_error:
165 139 assert response.status_int == 200
166 140 response.mustcontain('Value must be one of')
167 141 else:
168 142 if expect_error:
169 143 msg = 'An error occurred during permissions saving'
170 144 else:
171 145 msg = 'User Group global permissions updated successfully'
172 ug = UserGroup.get_by_group_name(users_group_name)
173 del perm_params['_method']
146 ug = UserGroup.get_by_group_name(user_group_name)
174 147 del perm_params['csrf_token']
175 148 del perm_params['inherit_default_permissions']
176 149 assert perm_params == ug.get_default_perms()
177 150 assert_session_flash(response, msg)
178 151
179 fixture.destroy_user_group(users_group_name)
180
181 def test_edit_autocomplete(self):
152 def test_edit_view(self, user_util):
182 153 self.log_user()
183 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
184 response = self.app.get(
185 url('edit_users_group', user_group_id=ug.users_group_id))
186 fixture.destroy_user_group(TEST_USER_GROUP)
154
155 user_group = user_util.create_user_group()
156 self.app.get(
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 217 def test_update_members_from_user_ids(self, user_regular):
189 218 uid = user_regular.user_id
190 219 username = user_regular.username
191 220 self.log_user()
192 221
193 222 user_group = fixture.create_user_group('test_gr_ids')
194 223 assert user_group.members == []
195 224 assert user_group.user != user_regular
196 225 expected_active_state = not user_group.users_group_active
197 226
198 227 form_data = [
199 228 ('csrf_token', self.csrf_token),
200 ('_method', 'put'),
201 229 ('user', username),
202 230 ('users_group_name', 'changed_name'),
203 231 ('users_group_active', expected_active_state),
204 232 ('user_group_description', 'changed_description'),
205 233
206 234 ('__start__', 'user_group_members:sequence'),
207 235 ('__start__', 'member:mapping'),
208 236 ('member_user_id', uid),
209 237 ('type', 'existing'),
210 238 ('__end__', 'member:mapping'),
211 239 ('__end__', 'user_group_members:sequence'),
212 240 ]
213 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 245 user_group = UserGroup.get(ugid)
217 246 assert user_group
218 247
219 248 assert user_group.members[0].user_id == uid
220 249 assert user_group.user_id == uid
221 250 assert 'changed_name' in user_group.users_group_name
222 251 assert 'changed_description' in user_group.user_group_description
223 252 assert user_group.users_group_active == expected_active_state
224 253
225 254 fixture.destroy_user_group(user_group)
@@ -1,535 +1,536 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 import traceback
26 26 from collections import OrderedDict
27 27
28 28 from paste.registry import RegistryManager
29 29 from paste.gzipper import make_gzip_middleware
30 30 from pylons.wsgiapp import PylonsApp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.wsgi import wsgiapp
35 35 from pyramid.httpexceptions import (
36 36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 37 from pyramid.events import ApplicationCreated
38 38 from pyramid.renderers import render_to_response
39 39 from routes.middleware import RoutesMiddleware
40 40 import rhodecode
41 41
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47
48 48 from rhodecode.lib.vcs import VCSCommunicationError
49 49 from rhodecode.lib.exceptions import VCSServerUnavailable
50 50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 51 from rhodecode.lib.middleware.error_handling import (
52 52 PylonsErrorHandlingMiddleware)
53 53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 54 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 57 from rhodecode.subscribers import (
58 58 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 59 write_metadata_if_needed)
60 60
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 66 # for certain routes which won't go to pylons to - eg. static files, debugger
67 67 # it is only needed for the pylons migration and can be removed once complete
68 68 class SkippableRoutesMiddleware(RoutesMiddleware):
69 69 """ Routes middleware that allows you to skip prefixes """
70 70
71 71 def __init__(self, *args, **kw):
72 72 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74 74
75 75 def __call__(self, environ, start_response):
76 76 for prefix in self.skip_prefixes:
77 77 if environ['PATH_INFO'].startswith(prefix):
78 78 # added to avoid the case when a missing /_static route falls
79 79 # through to pylons and causes an exception as pylons is
80 80 # expecting wsgiorg.routingargs to be set in the environ
81 81 # by RoutesMiddleware.
82 82 if 'wsgiorg.routing_args' not in environ:
83 83 environ['wsgiorg.routing_args'] = (None, {})
84 84 return self.app(environ, start_response)
85 85
86 86 return super(SkippableRoutesMiddleware, self).__call__(
87 87 environ, start_response)
88 88
89 89
90 90 def make_app(global_conf, static_files=True, **app_conf):
91 91 """Create a Pylons WSGI application and return it
92 92
93 93 ``global_conf``
94 94 The inherited configuration for this application. Normally from
95 95 the [DEFAULT] section of the Paste ini file.
96 96
97 97 ``app_conf``
98 98 The application's local configuration. Normally specified in
99 99 the [app:<name>] section of the Paste ini file (where <name>
100 100 defaults to main).
101 101
102 102 """
103 103 # Apply compatibility patches
104 104 patches.kombu_1_5_1_python_2_7_11()
105 105 patches.inspect_getargspec()
106 106
107 107 # Configure the Pylons environment
108 108 config = load_environment(global_conf, app_conf)
109 109
110 110 # The Pylons WSGI app
111 111 app = PylonsApp(config=config)
112 112
113 113 # Establish the Registry for this application
114 114 app = RegistryManager(app)
115 115
116 116 app.config = config
117 117
118 118 return app
119 119
120 120
121 121 def make_pyramid_app(global_config, **settings):
122 122 """
123 123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 124 application.
125 125
126 126 Specials:
127 127
128 128 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 129 frameworks functional. This involves moving some WSGI middlewares around
130 130 and providing access to some data internals, so that the old code is
131 131 still functional.
132 132
133 133 * The application can also be integrated like a plugin via the call to
134 134 `includeme`. This is accompanied with the other utility functions which
135 135 are called. Changing this should be done with great care to not break
136 136 cases when these fragments are assembled from another place.
137 137
138 138 """
139 139 # The edition string should be available in pylons too, so we add it here
140 140 # before copying the settings.
141 141 settings.setdefault('rhodecode.edition', 'Community Edition')
142 142
143 143 # As long as our Pylons application does expect "unprepared" settings, make
144 144 # sure that we keep an unmodified copy. This avoids unintentional change of
145 145 # behavior in the old application.
146 146 settings_pylons = settings.copy()
147 147
148 148 sanitize_settings_and_apply_defaults(settings)
149 149 config = Configurator(settings=settings)
150 150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
151 151
152 152 load_pyramid_environment(global_config, settings)
153 153
154 154 includeme_first(config)
155 155 includeme(config)
156 156
157 157 pyramid_app = config.make_wsgi_app()
158 158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
159 159 pyramid_app.config = config
160 160
161 161 # creating the app uses a connection - return it after we are done
162 162 meta.Session.remove()
163 163
164 164 return pyramid_app
165 165
166 166
167 167 def make_not_found_view(config):
168 168 """
169 169 This creates the view which should be registered as not-found-view to
170 170 pyramid. Basically it contains of the old pylons app, converted to a view.
171 171 Additionally it is wrapped by some other middlewares.
172 172 """
173 173 settings = config.registry.settings
174 174 vcs_server_enabled = settings['vcs.server.enable']
175 175
176 176 # Make pylons app from unprepared settings.
177 177 pylons_app = make_app(
178 178 config.registry._pylons_compat_global_config,
179 179 **config.registry._pylons_compat_settings)
180 180 config.registry._pylons_compat_config = pylons_app.config
181 181
182 182 # Appenlight monitoring.
183 183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
184 184 pylons_app, settings)
185 185
186 186 # The pylons app is executed inside of the pyramid 404 exception handler.
187 187 # Exceptions which are raised inside of it are not handled by pyramid
188 188 # again. Therefore we add a middleware that invokes the error handler in
189 189 # case of an exception or error response. This way we return proper error
190 190 # HTML pages in case of an error.
191 191 reraise = (settings.get('debugtoolbar.enabled', False) or
192 192 rhodecode.disable_error_handler)
193 193 pylons_app = PylonsErrorHandlingMiddleware(
194 194 pylons_app, error_handler, reraise)
195 195
196 196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
197 197 # view to handle the request. Therefore it is wrapped around the pylons
198 198 # app. It has to be outside of the error handling otherwise error responses
199 199 # from the vcsserver are converted to HTML error pages. This confuses the
200 200 # command line tools and the user won't get a meaningful error message.
201 201 if vcs_server_enabled:
202 202 pylons_app = VCSMiddleware(
203 203 pylons_app, settings, appenlight_client, registry=config.registry)
204 204
205 205 # Convert WSGI app to pyramid view and return it.
206 206 return wsgiapp(pylons_app)
207 207
208 208
209 209 def add_pylons_compat_data(registry, global_config, settings):
210 210 """
211 211 Attach data to the registry to support the Pylons integration.
212 212 """
213 213 registry._pylons_compat_global_config = global_config
214 214 registry._pylons_compat_settings = settings
215 215
216 216
217 217 def error_handler(exception, request):
218 218 import rhodecode
219 219 from rhodecode.lib import helpers
220 220
221 221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
222 222
223 223 base_response = HTTPInternalServerError()
224 224 # prefer original exception for the response since it may have headers set
225 225 if isinstance(exception, HTTPException):
226 226 base_response = exception
227 227 elif isinstance(exception, VCSCommunicationError):
228 228 base_response = VCSServerUnavailable()
229 229
230 230 def is_http_error(response):
231 231 # error which should have traceback
232 232 return response.status_code > 499
233 233
234 234 if is_http_error(base_response):
235 235 log.exception(
236 236 'error occurred handling this request for path: %s', request.path)
237 237
238 238 c = AttributeDict()
239 239 c.error_message = base_response.status
240 240 c.error_explanation = base_response.explanation or str(base_response)
241 241 c.visual = AttributeDict()
242 242
243 243 c.visual.rhodecode_support_url = (
244 244 request.registry.settings.get('rhodecode_support_url') or
245 245 request.route_url('rhodecode_support')
246 246 )
247 247 c.redirect_time = 0
248 248 c.rhodecode_name = rhodecode_title
249 249 if not c.rhodecode_name:
250 250 c.rhodecode_name = 'Rhodecode'
251 251
252 252 c.causes = []
253 253 if hasattr(base_response, 'causes'):
254 254 c.causes = base_response.causes
255 255 c.messages = helpers.flash.pop_messages(request=request)
256 256 c.traceback = traceback.format_exc()
257 257 response = render_to_response(
258 258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
259 259 response=base_response)
260 260
261 261 return response
262 262
263 263
264 264 def includeme(config):
265 265 settings = config.registry.settings
266 266
267 267 # plugin information
268 268 config.registry.rhodecode_plugins = OrderedDict()
269 269
270 270 config.add_directive(
271 271 'register_rhodecode_plugin', register_rhodecode_plugin)
272 272
273 273 if asbool(settings.get('appenlight', 'false')):
274 274 config.include('appenlight_client.ext.pyramid_tween')
275 275
276 276 if 'mako.default_filters' not in settings:
277 277 # set custom default filters if we don't have it defined
278 278 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
279 279 settings['mako.default_filters'] = 'h_filter'
280 280
281 281 # Includes which are required. The application would fail without them.
282 282 config.include('pyramid_mako')
283 283 config.include('pyramid_beaker')
284 284
285 285 config.include('rhodecode.authentication')
286 286 config.include('rhodecode.integrations')
287 287
288 288 # apps
289 289 config.include('rhodecode.apps._base')
290 290 config.include('rhodecode.apps.ops')
291 291
292 292 config.include('rhodecode.apps.admin')
293 293 config.include('rhodecode.apps.channelstream')
294 294 config.include('rhodecode.apps.login')
295 295 config.include('rhodecode.apps.home')
296 296 config.include('rhodecode.apps.journal')
297 297 config.include('rhodecode.apps.repository')
298 298 config.include('rhodecode.apps.repo_group')
299 config.include('rhodecode.apps.user_group')
299 300 config.include('rhodecode.apps.search')
300 301 config.include('rhodecode.apps.user_profile')
301 302 config.include('rhodecode.apps.my_account')
302 303 config.include('rhodecode.apps.svn_support')
303 304 config.include('rhodecode.apps.ssh_support')
304 305 config.include('rhodecode.apps.gist')
305 306
306 307 config.include('rhodecode.apps.debug_style')
307 308 config.include('rhodecode.tweens')
308 309 config.include('rhodecode.api')
309 310
310 311 config.add_route(
311 312 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
312 313
313 314 config.add_translation_dirs('rhodecode:i18n/')
314 315 settings['default_locale_name'] = settings.get('lang', 'en')
315 316
316 317 # Add subscribers.
317 318 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
318 319 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
319 320 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
320 321
321 322 config.add_request_method(
322 323 'rhodecode.lib.partial_renderer.get_partial_renderer',
323 324 'get_partial_renderer')
324 325
325 326 # events
326 327 # TODO(marcink): this should be done when pyramid migration is finished
327 328 # config.add_subscriber(
328 329 # 'rhodecode.integrations.integrations_event_handler',
329 330 # 'rhodecode.events.RhodecodeEvent')
330 331
331 332 # Set the authorization policy.
332 333 authz_policy = ACLAuthorizationPolicy()
333 334 config.set_authorization_policy(authz_policy)
334 335
335 336 # Set the default renderer for HTML templates to mako.
336 337 config.add_mako_renderer('.html')
337 338
338 339 config.add_renderer(
339 340 name='json_ext',
340 341 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
341 342
342 343 # include RhodeCode plugins
343 344 includes = aslist(settings.get('rhodecode.includes', []))
344 345 for inc in includes:
345 346 config.include(inc)
346 347
347 348 # This is the glue which allows us to migrate in chunks. By registering the
348 349 # pylons based application as the "Not Found" view in Pyramid, we will
349 350 # fallback to the old application each time the new one does not yet know
350 351 # how to handle a request.
351 352 config.add_notfound_view(make_not_found_view(config))
352 353
353 354 if not settings.get('debugtoolbar.enabled', False):
354 355 # disabled debugtoolbar handle all exceptions via the error_handlers
355 356 config.add_view(error_handler, context=Exception)
356 357
357 358 config.add_view(error_handler, context=HTTPError)
358 359
359 360
360 361 def includeme_first(config):
361 362 # redirect automatic browser favicon.ico requests to correct place
362 363 def favicon_redirect(context, request):
363 364 return HTTPFound(
364 365 request.static_path('rhodecode:public/images/favicon.ico'))
365 366
366 367 config.add_view(favicon_redirect, route_name='favicon')
367 368 config.add_route('favicon', '/favicon.ico')
368 369
369 370 def robots_redirect(context, request):
370 371 return HTTPFound(
371 372 request.static_path('rhodecode:public/robots.txt'))
372 373
373 374 config.add_view(robots_redirect, route_name='robots')
374 375 config.add_route('robots', '/robots.txt')
375 376
376 377 config.add_static_view(
377 378 '_static/deform', 'deform:static')
378 379 config.add_static_view(
379 380 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
380 381
381 382
382 383 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
383 384 """
384 385 Apply outer WSGI middlewares around the application.
385 386
386 387 Part of this has been moved up from the Pylons layer, so that the
387 388 data is also available if old Pylons code is hit through an already ported
388 389 view.
389 390 """
390 391 settings = config.registry.settings
391 392
392 393 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
393 394 pyramid_app = HttpsFixup(pyramid_app, settings)
394 395
395 396 # Add RoutesMiddleware to support the pylons compatibility tween during
396 397 # migration to pyramid.
397 398
398 399 # TODO(marcink): remove after migration to pyramid
399 400 if hasattr(config.registry, '_pylons_compat_config'):
400 401 routes_map = config.registry._pylons_compat_config['routes.map']
401 402 pyramid_app = SkippableRoutesMiddleware(
402 403 pyramid_app, routes_map,
403 404 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
404 405
405 406 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
406 407
407 408 if settings['gzip_responses']:
408 409 pyramid_app = make_gzip_middleware(
409 410 pyramid_app, settings, compress_level=1)
410 411
411 412 # this should be the outer most middleware in the wsgi stack since
412 413 # middleware like Routes make database calls
413 414 def pyramid_app_with_cleanup(environ, start_response):
414 415 try:
415 416 return pyramid_app(environ, start_response)
416 417 finally:
417 418 # Dispose current database session and rollback uncommitted
418 419 # transactions.
419 420 meta.Session.remove()
420 421
421 422 # In a single threaded mode server, on non sqlite db we should have
422 423 # '0 Current Checked out connections' at the end of a request,
423 424 # if not, then something, somewhere is leaving a connection open
424 425 pool = meta.Base.metadata.bind.engine.pool
425 426 log.debug('sa pool status: %s', pool.status())
426 427
427 428 return pyramid_app_with_cleanup
428 429
429 430
430 431 def sanitize_settings_and_apply_defaults(settings):
431 432 """
432 433 Applies settings defaults and does all type conversion.
433 434
434 435 We would move all settings parsing and preparation into this place, so that
435 436 we have only one place left which deals with this part. The remaining parts
436 437 of the application would start to rely fully on well prepared settings.
437 438
438 439 This piece would later be split up per topic to avoid a big fat monster
439 440 function.
440 441 """
441 442
442 443 # Pyramid's mako renderer has to search in the templates folder so that the
443 444 # old templates still work. Ported and new templates are expected to use
444 445 # real asset specifications for the includes.
445 446 mako_directories = settings.setdefault('mako.directories', [
446 447 # Base templates of the original Pylons application
447 448 'rhodecode:templates',
448 449 ])
449 450 log.debug(
450 451 "Using the following Mako template directories: %s",
451 452 mako_directories)
452 453
453 454 # Default includes, possible to change as a user
454 455 pyramid_includes = settings.setdefault('pyramid.includes', [
455 456 'rhodecode.lib.middleware.request_wrapper',
456 457 ])
457 458 log.debug(
458 459 "Using the following pyramid.includes: %s",
459 460 pyramid_includes)
460 461
461 462 # TODO: johbo: Re-think this, usually the call to config.include
462 463 # should allow to pass in a prefix.
463 464 settings.setdefault('rhodecode.api.url', '/_admin/api')
464 465
465 466 # Sanitize generic settings.
466 467 _list_setting(settings, 'default_encoding', 'UTF-8')
467 468 _bool_setting(settings, 'is_test', 'false')
468 469 _bool_setting(settings, 'gzip_responses', 'false')
469 470
470 471 # Call split out functions that sanitize settings for each topic.
471 472 _sanitize_appenlight_settings(settings)
472 473 _sanitize_vcs_settings(settings)
473 474
474 475 return settings
475 476
476 477
477 478 def _sanitize_appenlight_settings(settings):
478 479 _bool_setting(settings, 'appenlight', 'false')
479 480
480 481
481 482 def _sanitize_vcs_settings(settings):
482 483 """
483 484 Applies settings defaults and does type conversion for all VCS related
484 485 settings.
485 486 """
486 487 _string_setting(settings, 'vcs.svn.compatible_version', '')
487 488 _string_setting(settings, 'git_rev_filter', '--all')
488 489 _string_setting(settings, 'vcs.hooks.protocol', 'http')
489 490 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
490 491 _string_setting(settings, 'vcs.server', '')
491 492 _string_setting(settings, 'vcs.server.log_level', 'debug')
492 493 _string_setting(settings, 'vcs.server.protocol', 'http')
493 494 _bool_setting(settings, 'startup.import_repos', 'false')
494 495 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
495 496 _bool_setting(settings, 'vcs.server.enable', 'true')
496 497 _bool_setting(settings, 'vcs.start_server', 'false')
497 498 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
498 499 _int_setting(settings, 'vcs.connection_timeout', 3600)
499 500
500 501 # Support legacy values of vcs.scm_app_implementation. Legacy
501 502 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
502 503 # which is now mapped to 'http'.
503 504 scm_app_impl = settings['vcs.scm_app_implementation']
504 505 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
505 506 settings['vcs.scm_app_implementation'] = 'http'
506 507
507 508
508 509 def _int_setting(settings, name, default):
509 510 settings[name] = int(settings.get(name, default))
510 511
511 512
512 513 def _bool_setting(settings, name, default):
513 514 input = settings.get(name, default)
514 515 if isinstance(input, unicode):
515 516 input = input.encode('utf8')
516 517 settings[name] = asbool(input)
517 518
518 519
519 520 def _list_setting(settings, name, default):
520 521 raw_value = settings.get(name, default)
521 522
522 523 old_separator = ','
523 524 if old_separator in raw_value:
524 525 # If we get a comma separated list, pass it to our own function.
525 526 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
526 527 else:
527 528 # Otherwise we assume it uses pyramids space/newline separation.
528 529 settings[name] = aslist(raw_value)
529 530
530 531
531 532 def _string_setting(settings, name, default, lower=True):
532 533 value = settings.get(name, default)
533 534 if lower:
534 535 value = value.lower()
535 536 settings[name] = value
@@ -1,393 +1,355 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('show_user', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(
108 108 directory=config['pylons.paths']['controllers'],
109 109 always_scan=config['debug'])
110 110 rmap.minimization = False
111 111 rmap.explicit = False
112 112
113 113 from rhodecode.lib.utils2 import str2bool
114 114 from rhodecode.model import repo, repo_group
115 115
116 116 def check_repo(environ, match_dict):
117 117 """
118 118 check for valid repository for proper 404 handling
119 119
120 120 :param environ:
121 121 :param match_dict:
122 122 """
123 123 repo_name = match_dict.get('repo_name')
124 124
125 125 if match_dict.get('f_path'):
126 126 # fix for multiple initial slashes that causes errors
127 127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 128 repo_model = repo.RepoModel()
129 129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 130 # if we match quickly from database, short circuit the operation,
131 131 # and validate repo based on the type.
132 132 if by_name_match:
133 133 return True
134 134
135 135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 136 if by_id_match:
137 137 repo_name = by_id_match.repo_name
138 138 match_dict['repo_name'] = repo_name
139 139 return True
140 140
141 141 return False
142 142
143 143 def check_group(environ, match_dict):
144 144 """
145 145 check for valid repository group path for proper 404 handling
146 146
147 147 :param environ:
148 148 :param match_dict:
149 149 """
150 150 repo_group_name = match_dict.get('group_name')
151 151 repo_group_model = repo_group.RepoGroupModel()
152 152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 153 if by_name_match:
154 154 return True
155 155
156 156 return False
157 157
158 158 def check_user_group(environ, match_dict):
159 159 """
160 160 check for valid user group for proper 404 handling
161 161
162 162 :param environ:
163 163 :param match_dict:
164 164 """
165 165 return True
166 166
167 167 def check_int(environ, match_dict):
168 168 return match_dict.get('id').isdigit()
169 169
170 170
171 171 #==========================================================================
172 172 # CUSTOM ROUTES HERE
173 173 #==========================================================================
174 174
175 175 # ping and pylons error test
176 176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178 178
179 179 # ADMIN REPOSITORY GROUPS ROUTES
180 180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 181 controller='admin/repo_groups') as m:
182 182 m.connect('repo_groups', '/repo_groups',
183 183 action='create', conditions={'method': ['POST']})
184 184 m.connect('repo_groups', '/repo_groups',
185 185 action='index', conditions={'method': ['GET']})
186 186 m.connect('new_repo_group', '/repo_groups/new',
187 187 action='new', conditions={'method': ['GET']})
188 188 m.connect('update_repo_group', '/repo_groups/{group_name}',
189 189 action='update', conditions={'method': ['PUT'],
190 190 'function': check_group},
191 191 requirements=URL_NAME_REQUIREMENTS)
192 192
193 193 # EXTRAS REPO GROUP ROUTES
194 194 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
195 195 action='edit',
196 196 conditions={'method': ['GET'], 'function': check_group},
197 197 requirements=URL_NAME_REQUIREMENTS)
198 198 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
199 199 action='edit',
200 200 conditions={'method': ['PUT'], 'function': check_group},
201 201 requirements=URL_NAME_REQUIREMENTS)
202 202
203 203 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
204 204 action='edit_repo_group_advanced',
205 205 conditions={'method': ['GET'], 'function': check_group},
206 206 requirements=URL_NAME_REQUIREMENTS)
207 207 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
208 208 action='edit_repo_group_advanced',
209 209 conditions={'method': ['PUT'], 'function': check_group},
210 210 requirements=URL_NAME_REQUIREMENTS)
211 211
212 212 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
213 213 action='edit_repo_group_perms',
214 214 conditions={'method': ['GET'], 'function': check_group},
215 215 requirements=URL_NAME_REQUIREMENTS)
216 216 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
217 217 action='update_perms',
218 218 conditions={'method': ['PUT'], 'function': check_group},
219 219 requirements=URL_NAME_REQUIREMENTS)
220 220
221 221 m.connect('delete_repo_group', '/repo_groups/{group_name}',
222 222 action='delete', conditions={'method': ['DELETE'],
223 223 'function': check_group},
224 224 requirements=URL_NAME_REQUIREMENTS)
225 225
226 226 # ADMIN USER ROUTES
227 227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 228 controller='admin/users') as m:
229 229 m.connect('users', '/users',
230 230 action='create', conditions={'method': ['POST']})
231 231 m.connect('new_user', '/users/new',
232 232 action='new', conditions={'method': ['GET']})
233 233 m.connect('update_user', '/users/{user_id}',
234 234 action='update', conditions={'method': ['PUT']})
235 235 m.connect('delete_user', '/users/{user_id}',
236 236 action='delete', conditions={'method': ['DELETE']})
237 237 m.connect('edit_user', '/users/{user_id}/edit',
238 238 action='edit', conditions={'method': ['GET']}, jsroute=True)
239 239 m.connect('user', '/users/{user_id}',
240 240 action='show', conditions={'method': ['GET']})
241 241 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
242 242 action='reset_password', conditions={'method': ['POST']})
243 243 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
244 244 action='create_personal_repo_group', conditions={'method': ['POST']})
245 245
246 246 # EXTRAS USER ROUTES
247 247 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
248 248 action='edit_advanced', conditions={'method': ['GET']})
249 249 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
250 250 action='update_advanced', conditions={'method': ['PUT']})
251 251
252 252 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
253 253 action='edit_global_perms', conditions={'method': ['GET']})
254 254 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
255 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 257 # ADMIN DEFAULTS REST ROUTES
296 258 with rmap.submapper(path_prefix=ADMIN_PREFIX,
297 259 controller='admin/defaults') as m:
298 260 m.connect('admin_defaults_repositories', '/defaults/repositories',
299 261 action='update_repository_defaults', conditions={'method': ['POST']})
300 262 m.connect('admin_defaults_repositories', '/defaults/repositories',
301 263 action='index', conditions={'method': ['GET']})
302 264
303 265 # ADMIN SETTINGS ROUTES
304 266 with rmap.submapper(path_prefix=ADMIN_PREFIX,
305 267 controller='admin/settings') as m:
306 268
307 269 # default
308 270 m.connect('admin_settings', '/settings',
309 271 action='settings_global_update',
310 272 conditions={'method': ['POST']})
311 273 m.connect('admin_settings', '/settings',
312 274 action='settings_global', conditions={'method': ['GET']})
313 275
314 276 m.connect('admin_settings_vcs', '/settings/vcs',
315 277 action='settings_vcs_update',
316 278 conditions={'method': ['POST']})
317 279 m.connect('admin_settings_vcs', '/settings/vcs',
318 280 action='settings_vcs',
319 281 conditions={'method': ['GET']})
320 282 m.connect('admin_settings_vcs', '/settings/vcs',
321 283 action='delete_svn_pattern',
322 284 conditions={'method': ['DELETE']})
323 285
324 286 m.connect('admin_settings_mapping', '/settings/mapping',
325 287 action='settings_mapping_update',
326 288 conditions={'method': ['POST']})
327 289 m.connect('admin_settings_mapping', '/settings/mapping',
328 290 action='settings_mapping', conditions={'method': ['GET']})
329 291
330 292 m.connect('admin_settings_global', '/settings/global',
331 293 action='settings_global_update',
332 294 conditions={'method': ['POST']})
333 295 m.connect('admin_settings_global', '/settings/global',
334 296 action='settings_global', conditions={'method': ['GET']})
335 297
336 298 m.connect('admin_settings_visual', '/settings/visual',
337 299 action='settings_visual_update',
338 300 conditions={'method': ['POST']})
339 301 m.connect('admin_settings_visual', '/settings/visual',
340 302 action='settings_visual', conditions={'method': ['GET']})
341 303
342 304 m.connect('admin_settings_issuetracker',
343 305 '/settings/issue-tracker', action='settings_issuetracker',
344 306 conditions={'method': ['GET']})
345 307 m.connect('admin_settings_issuetracker_save',
346 308 '/settings/issue-tracker/save',
347 309 action='settings_issuetracker_save',
348 310 conditions={'method': ['POST']})
349 311 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
350 312 action='settings_issuetracker_test',
351 313 conditions={'method': ['POST']})
352 314 m.connect('admin_issuetracker_delete',
353 315 '/settings/issue-tracker/delete',
354 316 action='settings_issuetracker_delete',
355 317 conditions={'method': ['DELETE']})
356 318
357 319 m.connect('admin_settings_email', '/settings/email',
358 320 action='settings_email_update',
359 321 conditions={'method': ['POST']})
360 322 m.connect('admin_settings_email', '/settings/email',
361 323 action='settings_email', conditions={'method': ['GET']})
362 324
363 325 m.connect('admin_settings_hooks', '/settings/hooks',
364 326 action='settings_hooks_update',
365 327 conditions={'method': ['POST', 'DELETE']})
366 328 m.connect('admin_settings_hooks', '/settings/hooks',
367 329 action='settings_hooks', conditions={'method': ['GET']})
368 330
369 331 m.connect('admin_settings_search', '/settings/search',
370 332 action='settings_search', conditions={'method': ['GET']})
371 333
372 334 m.connect('admin_settings_supervisor', '/settings/supervisor',
373 335 action='settings_supervisor', conditions={'method': ['GET']})
374 336 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
375 337 action='settings_supervisor_log', conditions={'method': ['GET']})
376 338
377 339 m.connect('admin_settings_labs', '/settings/labs',
378 340 action='settings_labs_update',
379 341 conditions={'method': ['POST']})
380 342 m.connect('admin_settings_labs', '/settings/labs',
381 343 action='settings_labs', conditions={'method': ['GET']})
382 344
383 345 # ADMIN MY ACCOUNT
384 346 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 347 controller='admin/my_account') as m:
386 348
387 349 # NOTE(marcink): this needs to be kept for password force flag to be
388 350 # handled in pylons controllers, remove after full migration to pyramid
389 351 m.connect('my_account_password', '/my_account/password',
390 352 action='my_account_password', conditions={'method': ['GET']})
391 353
392 354
393 355 return rmap
@@ -1,2161 +1,2161 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import inspect
27 27 import collections
28 28 import fnmatch
29 29 import hashlib
30 30 import itertools
31 31 import logging
32 32 import random
33 33 import traceback
34 34 from functools import wraps
35 35
36 36 import ipaddress
37 37 from beaker.cache import cache_region
38 38 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 39 from pylons.i18n.translation import _
40 40 # NOTE(marcink): this has to be removed only after pyramid migration,
41 41 # replace with _ = request.translate
42 42 from sqlalchemy.orm.exc import ObjectDeletedError
43 43 from sqlalchemy.orm import joinedload
44 44 from zope.cachedescriptors.property import Lazy as LazyProperty
45 45
46 46 import rhodecode
47 47 from rhodecode.model import meta
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.db import (
51 51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
52 52 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
53 53 from rhodecode.lib import caches
54 54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
55 55 from rhodecode.lib.utils import (
56 56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
57 57 from rhodecode.lib.caching_query import FromCache
58 58
59 59
60 60 if rhodecode.is_unix:
61 61 import bcrypt
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65 csrf_token_key = "csrf_token"
66 66
67 67
68 68 class PasswordGenerator(object):
69 69 """
70 70 This is a simple class for generating password from different sets of
71 71 characters
72 72 usage::
73 73
74 74 passwd_gen = PasswordGenerator()
75 75 #print 8-letter password containing only big and small letters
76 76 of alphabet
77 77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
78 78 """
79 79 ALPHABETS_NUM = r'''1234567890'''
80 80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
81 81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
82 82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
83 83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
84 84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
85 85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
86 86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
87 87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
88 88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
89 89
90 90 def __init__(self, passwd=''):
91 91 self.passwd = passwd
92 92
93 93 def gen_password(self, length, type_=None):
94 94 if type_ is None:
95 95 type_ = self.ALPHABETS_FULL
96 96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
97 97 return self.passwd
98 98
99 99
100 100 class _RhodeCodeCryptoBase(object):
101 101 ENC_PREF = None
102 102
103 103 def hash_create(self, str_):
104 104 """
105 105 hash the string using
106 106
107 107 :param str_: password to hash
108 108 """
109 109 raise NotImplementedError
110 110
111 111 def hash_check_with_upgrade(self, password, hashed):
112 112 """
113 113 Returns tuple in which first element is boolean that states that
114 114 given password matches it's hashed version, and the second is new hash
115 115 of the password, in case this password should be migrated to new
116 116 cipher.
117 117 """
118 118 checked_hash = self.hash_check(password, hashed)
119 119 return checked_hash, None
120 120
121 121 def hash_check(self, password, hashed):
122 122 """
123 123 Checks matching password with it's hashed value.
124 124
125 125 :param password: password
126 126 :param hashed: password in hashed form
127 127 """
128 128 raise NotImplementedError
129 129
130 130 def _assert_bytes(self, value):
131 131 """
132 132 Passing in an `unicode` object can lead to hard to detect issues
133 133 if passwords contain non-ascii characters. Doing a type check
134 134 during runtime, so that such mistakes are detected early on.
135 135 """
136 136 if not isinstance(value, str):
137 137 raise TypeError(
138 138 "Bytestring required as input, got %r." % (value, ))
139 139
140 140
141 141 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
142 142 ENC_PREF = ('$2a$10', '$2b$10')
143 143
144 144 def hash_create(self, str_):
145 145 self._assert_bytes(str_)
146 146 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
147 147
148 148 def hash_check_with_upgrade(self, password, hashed):
149 149 """
150 150 Returns tuple in which first element is boolean that states that
151 151 given password matches it's hashed version, and the second is new hash
152 152 of the password, in case this password should be migrated to new
153 153 cipher.
154 154
155 155 This implements special upgrade logic which works like that:
156 156 - check if the given password == bcrypted hash, if yes then we
157 157 properly used password and it was already in bcrypt. Proceed
158 158 without any changes
159 159 - if bcrypt hash check is not working try with sha256. If hash compare
160 160 is ok, it means we using correct but old hashed password. indicate
161 161 hash change and proceed
162 162 """
163 163
164 164 new_hash = None
165 165
166 166 # regular pw check
167 167 password_match_bcrypt = self.hash_check(password, hashed)
168 168
169 169 # now we want to know if the password was maybe from sha256
170 170 # basically calling _RhodeCodeCryptoSha256().hash_check()
171 171 if not password_match_bcrypt:
172 172 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
173 173 new_hash = self.hash_create(password) # make new bcrypt hash
174 174 password_match_bcrypt = True
175 175
176 176 return password_match_bcrypt, new_hash
177 177
178 178 def hash_check(self, password, hashed):
179 179 """
180 180 Checks matching password with it's hashed value.
181 181
182 182 :param password: password
183 183 :param hashed: password in hashed form
184 184 """
185 185 self._assert_bytes(password)
186 186 try:
187 187 return bcrypt.hashpw(password, hashed) == hashed
188 188 except ValueError as e:
189 189 # we're having a invalid salt here probably, we should not crash
190 190 # just return with False as it would be a wrong password.
191 191 log.debug('Failed to check password hash using bcrypt %s',
192 192 safe_str(e))
193 193
194 194 return False
195 195
196 196
197 197 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
198 198 ENC_PREF = '_'
199 199
200 200 def hash_create(self, str_):
201 201 self._assert_bytes(str_)
202 202 return hashlib.sha256(str_).hexdigest()
203 203
204 204 def hash_check(self, password, hashed):
205 205 """
206 206 Checks matching password with it's hashed value.
207 207
208 208 :param password: password
209 209 :param hashed: password in hashed form
210 210 """
211 211 self._assert_bytes(password)
212 212 return hashlib.sha256(password).hexdigest() == hashed
213 213
214 214
215 215 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
216 216 ENC_PREF = '_'
217 217
218 218 def hash_create(self, str_):
219 219 self._assert_bytes(str_)
220 220 return hashlib.md5(str_).hexdigest()
221 221
222 222 def hash_check(self, password, hashed):
223 223 """
224 224 Checks matching password with it's hashed value.
225 225
226 226 :param password: password
227 227 :param hashed: password in hashed form
228 228 """
229 229 self._assert_bytes(password)
230 230 return hashlib.md5(password).hexdigest() == hashed
231 231
232 232
233 233 def crypto_backend():
234 234 """
235 235 Return the matching crypto backend.
236 236
237 237 Selection is based on if we run tests or not, we pick md5 backend to run
238 238 tests faster since BCRYPT is expensive to calculate
239 239 """
240 240 if rhodecode.is_test:
241 241 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
242 242 else:
243 243 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
244 244
245 245 return RhodeCodeCrypto
246 246
247 247
248 248 def get_crypt_password(password):
249 249 """
250 250 Create the hash of `password` with the active crypto backend.
251 251
252 252 :param password: The cleartext password.
253 253 :type password: unicode
254 254 """
255 255 password = safe_str(password)
256 256 return crypto_backend().hash_create(password)
257 257
258 258
259 259 def check_password(password, hashed):
260 260 """
261 261 Check if the value in `password` matches the hash in `hashed`.
262 262
263 263 :param password: The cleartext password.
264 264 :type password: unicode
265 265
266 266 :param hashed: The expected hashed version of the password.
267 267 :type hashed: The hash has to be passed in in text representation.
268 268 """
269 269 password = safe_str(password)
270 270 return crypto_backend().hash_check(password, hashed)
271 271
272 272
273 273 def generate_auth_token(data, salt=None):
274 274 """
275 275 Generates API KEY from given string
276 276 """
277 277
278 278 if salt is None:
279 279 salt = os.urandom(16)
280 280 return hashlib.sha1(safe_str(data) + salt).hexdigest()
281 281
282 282
283 283 class CookieStoreWrapper(object):
284 284
285 285 def __init__(self, cookie_store):
286 286 self.cookie_store = cookie_store
287 287
288 288 def __repr__(self):
289 289 return 'CookieStore<%s>' % (self.cookie_store)
290 290
291 291 def get(self, key, other=None):
292 292 if isinstance(self.cookie_store, dict):
293 293 return self.cookie_store.get(key, other)
294 294 elif isinstance(self.cookie_store, AuthUser):
295 295 return self.cookie_store.__dict__.get(key, other)
296 296
297 297
298 298 def _cached_perms_data(user_id, scope, user_is_admin,
299 299 user_inherit_default_permissions, explicit, algo,
300 300 calculate_super_admin):
301 301
302 302 permissions = PermissionCalculator(
303 303 user_id, scope, user_is_admin, user_inherit_default_permissions,
304 304 explicit, algo, calculate_super_admin)
305 305 return permissions.calculate()
306 306
307 307
308 308 class PermOrigin(object):
309 309 SUPER_ADMIN = 'superadmin'
310 310
311 311 REPO_USER = 'user:%s'
312 312 REPO_USERGROUP = 'usergroup:%s'
313 313 REPO_OWNER = 'repo.owner'
314 314 REPO_DEFAULT = 'repo.default'
315 315 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
316 316 REPO_PRIVATE = 'repo.private'
317 317
318 318 REPOGROUP_USER = 'user:%s'
319 319 REPOGROUP_USERGROUP = 'usergroup:%s'
320 320 REPOGROUP_OWNER = 'group.owner'
321 321 REPOGROUP_DEFAULT = 'group.default'
322 322 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
323 323
324 324 USERGROUP_USER = 'user:%s'
325 325 USERGROUP_USERGROUP = 'usergroup:%s'
326 326 USERGROUP_OWNER = 'usergroup.owner'
327 327 USERGROUP_DEFAULT = 'usergroup.default'
328 328 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
329 329
330 330
331 331 class PermOriginDict(dict):
332 332 """
333 333 A special dict used for tracking permissions along with their origins.
334 334
335 335 `__setitem__` has been overridden to expect a tuple(perm, origin)
336 336 `__getitem__` will return only the perm
337 337 `.perm_origin_stack` will return the stack of (perm, origin) set per key
338 338
339 339 >>> perms = PermOriginDict()
340 340 >>> perms['resource'] = 'read', 'default'
341 341 >>> perms['resource']
342 342 'read'
343 343 >>> perms['resource'] = 'write', 'admin'
344 344 >>> perms['resource']
345 345 'write'
346 346 >>> perms.perm_origin_stack
347 347 {'resource': [('read', 'default'), ('write', 'admin')]}
348 348 """
349 349
350 350 def __init__(self, *args, **kw):
351 351 dict.__init__(self, *args, **kw)
352 352 self.perm_origin_stack = collections.OrderedDict()
353 353
354 354 def __setitem__(self, key, (perm, origin)):
355 355 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
356 356 dict.__setitem__(self, key, perm)
357 357
358 358
359 359 class PermissionCalculator(object):
360 360
361 361 def __init__(
362 362 self, user_id, scope, user_is_admin,
363 363 user_inherit_default_permissions, explicit, algo,
364 364 calculate_super_admin=False):
365 365
366 366 self.user_id = user_id
367 367 self.user_is_admin = user_is_admin
368 368 self.inherit_default_permissions = user_inherit_default_permissions
369 369 self.explicit = explicit
370 370 self.algo = algo
371 371 self.calculate_super_admin = calculate_super_admin
372 372
373 373 scope = scope or {}
374 374 self.scope_repo_id = scope.get('repo_id')
375 375 self.scope_repo_group_id = scope.get('repo_group_id')
376 376 self.scope_user_group_id = scope.get('user_group_id')
377 377
378 378 self.default_user_id = User.get_default_user(cache=True).user_id
379 379
380 380 self.permissions_repositories = PermOriginDict()
381 381 self.permissions_repository_groups = PermOriginDict()
382 382 self.permissions_user_groups = PermOriginDict()
383 383 self.permissions_global = set()
384 384
385 385 self.default_repo_perms = Permission.get_default_repo_perms(
386 386 self.default_user_id, self.scope_repo_id)
387 387 self.default_repo_groups_perms = Permission.get_default_group_perms(
388 388 self.default_user_id, self.scope_repo_group_id)
389 389 self.default_user_group_perms = \
390 390 Permission.get_default_user_group_perms(
391 391 self.default_user_id, self.scope_user_group_id)
392 392
393 393 def calculate(self):
394 394 if self.user_is_admin and not self.calculate_super_admin:
395 395 return self._admin_permissions()
396 396
397 397 self._calculate_global_default_permissions()
398 398 self._calculate_global_permissions()
399 399 self._calculate_default_permissions()
400 400 self._calculate_repository_permissions()
401 401 self._calculate_repository_group_permissions()
402 402 self._calculate_user_group_permissions()
403 403 return self._permission_structure()
404 404
405 405 def _admin_permissions(self):
406 406 """
407 407 admin user have all default rights for repositories
408 408 and groups set to admin
409 409 """
410 410 self.permissions_global.add('hg.admin')
411 411 self.permissions_global.add('hg.create.write_on_repogroup.true')
412 412
413 413 # repositories
414 414 for perm in self.default_repo_perms:
415 415 r_k = perm.UserRepoToPerm.repository.repo_name
416 416 p = 'repository.admin'
417 417 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
418 418
419 419 # repository groups
420 420 for perm in self.default_repo_groups_perms:
421 421 rg_k = perm.UserRepoGroupToPerm.group.group_name
422 422 p = 'group.admin'
423 423 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
424 424
425 425 # user groups
426 426 for perm in self.default_user_group_perms:
427 427 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
428 428 p = 'usergroup.admin'
429 429 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
430 430
431 431 return self._permission_structure()
432 432
433 433 def _calculate_global_default_permissions(self):
434 434 """
435 435 global permissions taken from the default user
436 436 """
437 437 default_global_perms = UserToPerm.query()\
438 438 .filter(UserToPerm.user_id == self.default_user_id)\
439 439 .options(joinedload(UserToPerm.permission))
440 440
441 441 for perm in default_global_perms:
442 442 self.permissions_global.add(perm.permission.permission_name)
443 443
444 444 if self.user_is_admin:
445 445 self.permissions_global.add('hg.admin')
446 446 self.permissions_global.add('hg.create.write_on_repogroup.true')
447 447
448 448 def _calculate_global_permissions(self):
449 449 """
450 450 Set global system permissions with user permissions or permissions
451 451 taken from the user groups of the current user.
452 452
453 453 The permissions include repo creating, repo group creating, forking
454 454 etc.
455 455 """
456 456
457 457 # now we read the defined permissions and overwrite what we have set
458 458 # before those can be configured from groups or users explicitly.
459 459
460 460 # TODO: johbo: This seems to be out of sync, find out the reason
461 461 # for the comment below and update it.
462 462
463 463 # In case we want to extend this list we should be always in sync with
464 464 # User.DEFAULT_USER_PERMISSIONS definitions
465 465 _configurable = frozenset([
466 466 'hg.fork.none', 'hg.fork.repository',
467 467 'hg.create.none', 'hg.create.repository',
468 468 'hg.usergroup.create.false', 'hg.usergroup.create.true',
469 469 'hg.repogroup.create.false', 'hg.repogroup.create.true',
470 470 'hg.create.write_on_repogroup.false',
471 471 'hg.create.write_on_repogroup.true',
472 472 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
473 473 ])
474 474
475 475 # USER GROUPS comes first user group global permissions
476 476 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
477 477 .options(joinedload(UserGroupToPerm.permission))\
478 478 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
479 479 UserGroupMember.users_group_id))\
480 480 .filter(UserGroupMember.user_id == self.user_id)\
481 481 .order_by(UserGroupToPerm.users_group_id)\
482 482 .all()
483 483
484 484 # need to group here by groups since user can be in more than
485 485 # one group, so we get all groups
486 486 _explicit_grouped_perms = [
487 487 [x, list(y)] for x, y in
488 488 itertools.groupby(user_perms_from_users_groups,
489 489 lambda _x: _x.users_group)]
490 490
491 491 for gr, perms in _explicit_grouped_perms:
492 492 # since user can be in multiple groups iterate over them and
493 493 # select the lowest permissions first (more explicit)
494 494 # TODO: marcink: do this^^
495 495
496 496 # group doesn't inherit default permissions so we actually set them
497 497 if not gr.inherit_default_permissions:
498 498 # NEED TO IGNORE all previously set configurable permissions
499 499 # and replace them with explicitly set from this user
500 500 # group permissions
501 501 self.permissions_global = self.permissions_global.difference(
502 502 _configurable)
503 503 for perm in perms:
504 504 self.permissions_global.add(perm.permission.permission_name)
505 505
506 506 # user explicit global permissions
507 507 user_perms = Session().query(UserToPerm)\
508 508 .options(joinedload(UserToPerm.permission))\
509 509 .filter(UserToPerm.user_id == self.user_id).all()
510 510
511 511 if not self.inherit_default_permissions:
512 512 # NEED TO IGNORE all configurable permissions and
513 513 # replace them with explicitly set from this user permissions
514 514 self.permissions_global = self.permissions_global.difference(
515 515 _configurable)
516 516 for perm in user_perms:
517 517 self.permissions_global.add(perm.permission.permission_name)
518 518
519 519 def _calculate_default_permissions(self):
520 520 """
521 521 Set default user permissions for repositories, repository groups
522 522 taken from the default user.
523 523
524 524 Calculate inheritance of object permissions based on what we have now
525 525 in GLOBAL permissions. We check if .false is in GLOBAL since this is
526 526 explicitly set. Inherit is the opposite of .false being there.
527 527
528 528 .. note::
529 529
530 530 the syntax is little bit odd but what we need to check here is
531 531 the opposite of .false permission being in the list so even for
532 532 inconsistent state when both .true/.false is there
533 533 .false is more important
534 534
535 535 """
536 536 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
537 537 in self.permissions_global)
538 538
539 539 # defaults for repositories, taken from `default` user permissions
540 540 # on given repo
541 541 for perm in self.default_repo_perms:
542 542 r_k = perm.UserRepoToPerm.repository.repo_name
543 543 p = perm.Permission.permission_name
544 544 o = PermOrigin.REPO_DEFAULT
545 545 self.permissions_repositories[r_k] = p, o
546 546
547 547 # if we decide this user isn't inheriting permissions from
548 548 # default user we set him to .none so only explicit
549 549 # permissions work
550 550 if not user_inherit_object_permissions:
551 551 p = 'repository.none'
552 552 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
553 553 self.permissions_repositories[r_k] = p, o
554 554
555 555 if perm.Repository.private and not (
556 556 perm.Repository.user_id == self.user_id):
557 557 # disable defaults for private repos,
558 558 p = 'repository.none'
559 559 o = PermOrigin.REPO_PRIVATE
560 560 self.permissions_repositories[r_k] = p, o
561 561
562 562 elif perm.Repository.user_id == self.user_id:
563 563 # set admin if owner
564 564 p = 'repository.admin'
565 565 o = PermOrigin.REPO_OWNER
566 566 self.permissions_repositories[r_k] = p, o
567 567
568 568 if self.user_is_admin:
569 569 p = 'repository.admin'
570 570 o = PermOrigin.SUPER_ADMIN
571 571 self.permissions_repositories[r_k] = p, o
572 572
573 573 # defaults for repository groups taken from `default` user permission
574 574 # on given group
575 575 for perm in self.default_repo_groups_perms:
576 576 rg_k = perm.UserRepoGroupToPerm.group.group_name
577 577 p = perm.Permission.permission_name
578 578 o = PermOrigin.REPOGROUP_DEFAULT
579 579 self.permissions_repository_groups[rg_k] = p, o
580 580
581 581 # if we decide this user isn't inheriting permissions from default
582 582 # user we set him to .none so only explicit permissions work
583 583 if not user_inherit_object_permissions:
584 584 p = 'group.none'
585 585 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
586 586 self.permissions_repository_groups[rg_k] = p, o
587 587
588 588 if perm.RepoGroup.user_id == self.user_id:
589 589 # set admin if owner
590 590 p = 'group.admin'
591 591 o = PermOrigin.REPOGROUP_OWNER
592 592 self.permissions_repository_groups[rg_k] = p, o
593 593
594 594 if self.user_is_admin:
595 595 p = 'group.admin'
596 596 o = PermOrigin.SUPER_ADMIN
597 597 self.permissions_repository_groups[rg_k] = p, o
598 598
599 599 # defaults for user groups taken from `default` user permission
600 600 # on given user group
601 601 for perm in self.default_user_group_perms:
602 602 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
603 603 p = perm.Permission.permission_name
604 604 o = PermOrigin.USERGROUP_DEFAULT
605 605 self.permissions_user_groups[u_k] = p, o
606 606
607 607 # if we decide this user isn't inheriting permissions from default
608 608 # user we set him to .none so only explicit permissions work
609 609 if not user_inherit_object_permissions:
610 610 p = 'usergroup.none'
611 611 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
612 612 self.permissions_user_groups[u_k] = p, o
613 613
614 614 if perm.UserGroup.user_id == self.user_id:
615 615 # set admin if owner
616 616 p = 'usergroup.admin'
617 617 o = PermOrigin.USERGROUP_OWNER
618 618 self.permissions_user_groups[u_k] = p, o
619 619
620 620 if self.user_is_admin:
621 621 p = 'usergroup.admin'
622 622 o = PermOrigin.SUPER_ADMIN
623 623 self.permissions_user_groups[u_k] = p, o
624 624
625 625 def _calculate_repository_permissions(self):
626 626 """
627 627 Repository permissions for the current user.
628 628
629 629 Check if the user is part of user groups for this repository and
630 630 fill in the permission from it. `_choose_permission` decides of which
631 631 permission should be selected based on selected method.
632 632 """
633 633
634 634 # user group for repositories permissions
635 635 user_repo_perms_from_user_group = Permission\
636 636 .get_default_repo_perms_from_user_group(
637 637 self.user_id, self.scope_repo_id)
638 638
639 639 multiple_counter = collections.defaultdict(int)
640 640 for perm in user_repo_perms_from_user_group:
641 641 r_k = perm.UserGroupRepoToPerm.repository.repo_name
642 642 multiple_counter[r_k] += 1
643 643 p = perm.Permission.permission_name
644 644 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
645 645 .users_group.users_group_name
646 646
647 647 if multiple_counter[r_k] > 1:
648 648 cur_perm = self.permissions_repositories[r_k]
649 649 p = self._choose_permission(p, cur_perm)
650 650
651 651 self.permissions_repositories[r_k] = p, o
652 652
653 653 if perm.Repository.user_id == self.user_id:
654 654 # set admin if owner
655 655 p = 'repository.admin'
656 656 o = PermOrigin.REPO_OWNER
657 657 self.permissions_repositories[r_k] = p, o
658 658
659 659 if self.user_is_admin:
660 660 p = 'repository.admin'
661 661 o = PermOrigin.SUPER_ADMIN
662 662 self.permissions_repositories[r_k] = p, o
663 663
664 664 # user explicit permissions for repositories, overrides any specified
665 665 # by the group permission
666 666 user_repo_perms = Permission.get_default_repo_perms(
667 667 self.user_id, self.scope_repo_id)
668 668 for perm in user_repo_perms:
669 669 r_k = perm.UserRepoToPerm.repository.repo_name
670 670 p = perm.Permission.permission_name
671 671 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
672 672
673 673 if not self.explicit:
674 674 cur_perm = self.permissions_repositories.get(
675 675 r_k, 'repository.none')
676 676 p = self._choose_permission(p, cur_perm)
677 677
678 678 self.permissions_repositories[r_k] = p, o
679 679
680 680 if perm.Repository.user_id == self.user_id:
681 681 # set admin if owner
682 682 p = 'repository.admin'
683 683 o = PermOrigin.REPO_OWNER
684 684 self.permissions_repositories[r_k] = p, o
685 685
686 686 if self.user_is_admin:
687 687 p = 'repository.admin'
688 688 o = PermOrigin.SUPER_ADMIN
689 689 self.permissions_repositories[r_k] = p, o
690 690
691 691 def _calculate_repository_group_permissions(self):
692 692 """
693 693 Repository group permissions for the current user.
694 694
695 695 Check if the user is part of user groups for repository groups and
696 696 fill in the permissions from it. `_choose_permission` decides of which
697 697 permission should be selected based on selected method.
698 698 """
699 699 # user group for repo groups permissions
700 700 user_repo_group_perms_from_user_group = Permission\
701 701 .get_default_group_perms_from_user_group(
702 702 self.user_id, self.scope_repo_group_id)
703 703
704 704 multiple_counter = collections.defaultdict(int)
705 705 for perm in user_repo_group_perms_from_user_group:
706 706 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
707 707 multiple_counter[rg_k] += 1
708 708 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
709 709 .users_group.users_group_name
710 710 p = perm.Permission.permission_name
711 711
712 712 if multiple_counter[rg_k] > 1:
713 713 cur_perm = self.permissions_repository_groups[rg_k]
714 714 p = self._choose_permission(p, cur_perm)
715 715 self.permissions_repository_groups[rg_k] = p, o
716 716
717 717 if perm.RepoGroup.user_id == self.user_id:
718 718 # set admin if owner, even for member of other user group
719 719 p = 'group.admin'
720 720 o = PermOrigin.REPOGROUP_OWNER
721 721 self.permissions_repository_groups[rg_k] = p, o
722 722
723 723 if self.user_is_admin:
724 724 p = 'group.admin'
725 725 o = PermOrigin.SUPER_ADMIN
726 726 self.permissions_repository_groups[rg_k] = p, o
727 727
728 728 # user explicit permissions for repository groups
729 729 user_repo_groups_perms = Permission.get_default_group_perms(
730 730 self.user_id, self.scope_repo_group_id)
731 731 for perm in user_repo_groups_perms:
732 732 rg_k = perm.UserRepoGroupToPerm.group.group_name
733 733 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
734 734 .user.username
735 735 p = perm.Permission.permission_name
736 736
737 737 if not self.explicit:
738 738 cur_perm = self.permissions_repository_groups.get(
739 739 rg_k, 'group.none')
740 740 p = self._choose_permission(p, cur_perm)
741 741
742 742 self.permissions_repository_groups[rg_k] = p, o
743 743
744 744 if perm.RepoGroup.user_id == self.user_id:
745 745 # set admin if owner
746 746 p = 'group.admin'
747 747 o = PermOrigin.REPOGROUP_OWNER
748 748 self.permissions_repository_groups[rg_k] = p, o
749 749
750 750 if self.user_is_admin:
751 751 p = 'group.admin'
752 752 o = PermOrigin.SUPER_ADMIN
753 753 self.permissions_repository_groups[rg_k] = p, o
754 754
755 755 def _calculate_user_group_permissions(self):
756 756 """
757 757 User group permissions for the current user.
758 758 """
759 759 # user group for user group permissions
760 760 user_group_from_user_group = Permission\
761 761 .get_default_user_group_perms_from_user_group(
762 762 self.user_id, self.scope_user_group_id)
763 763
764 764 multiple_counter = collections.defaultdict(int)
765 765 for perm in user_group_from_user_group:
766 766 ug_k = perm.UserGroupUserGroupToPerm\
767 767 .target_user_group.users_group_name
768 768 multiple_counter[ug_k] += 1
769 769 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
770 770 .user_group.users_group_name
771 771 p = perm.Permission.permission_name
772 772
773 773 if multiple_counter[ug_k] > 1:
774 774 cur_perm = self.permissions_user_groups[ug_k]
775 775 p = self._choose_permission(p, cur_perm)
776 776
777 777 self.permissions_user_groups[ug_k] = p, o
778 778
779 779 if perm.UserGroup.user_id == self.user_id:
780 780 # set admin if owner, even for member of other user group
781 781 p = 'usergroup.admin'
782 782 o = PermOrigin.USERGROUP_OWNER
783 783 self.permissions_user_groups[ug_k] = p, o
784 784
785 785 if self.user_is_admin:
786 786 p = 'usergroup.admin'
787 787 o = PermOrigin.SUPER_ADMIN
788 788 self.permissions_user_groups[ug_k] = p, o
789 789
790 790 # user explicit permission for user groups
791 791 user_user_groups_perms = Permission.get_default_user_group_perms(
792 792 self.user_id, self.scope_user_group_id)
793 793 for perm in user_user_groups_perms:
794 794 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
795 795 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
796 796 .user.username
797 797 p = perm.Permission.permission_name
798 798
799 799 if not self.explicit:
800 800 cur_perm = self.permissions_user_groups.get(
801 801 ug_k, 'usergroup.none')
802 802 p = self._choose_permission(p, cur_perm)
803 803
804 804 self.permissions_user_groups[ug_k] = p, o
805 805
806 806 if perm.UserGroup.user_id == self.user_id:
807 807 # set admin if owner
808 808 p = 'usergroup.admin'
809 809 o = PermOrigin.USERGROUP_OWNER
810 810 self.permissions_user_groups[ug_k] = p, o
811 811
812 812 if self.user_is_admin:
813 813 p = 'usergroup.admin'
814 814 o = PermOrigin.SUPER_ADMIN
815 815 self.permissions_user_groups[ug_k] = p, o
816 816
817 817 def _choose_permission(self, new_perm, cur_perm):
818 818 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
819 819 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
820 820 if self.algo == 'higherwin':
821 821 if new_perm_val > cur_perm_val:
822 822 return new_perm
823 823 return cur_perm
824 824 elif self.algo == 'lowerwin':
825 825 if new_perm_val < cur_perm_val:
826 826 return new_perm
827 827 return cur_perm
828 828
829 829 def _permission_structure(self):
830 830 return {
831 831 'global': self.permissions_global,
832 832 'repositories': self.permissions_repositories,
833 833 'repositories_groups': self.permissions_repository_groups,
834 834 'user_groups': self.permissions_user_groups,
835 835 }
836 836
837 837
838 838 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
839 839 """
840 840 Check if given controller_name is in whitelist of auth token access
841 841 """
842 842 if not whitelist:
843 843 from rhodecode import CONFIG
844 844 whitelist = aslist(
845 845 CONFIG.get('api_access_controllers_whitelist'), sep=',')
846 846 # backward compat translation
847 847 compat = {
848 848 # old controller, new VIEW
849 849 'ChangesetController:*': 'RepoCommitsView:*',
850 850 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
851 851 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
852 852 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
853 853 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
854 854 'GistsController:*': 'GistView:*',
855 855 }
856 856
857 857 log.debug(
858 858 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
859 859 auth_token_access_valid = False
860 860
861 861 for entry in whitelist:
862 862 token_match = True
863 863 if entry in compat:
864 864 # translate from old Controllers to Pyramid Views
865 865 entry = compat[entry]
866 866
867 867 if '@' in entry:
868 868 # specific AuthToken
869 869 entry, allowed_token = entry.split('@', 1)
870 870 token_match = auth_token == allowed_token
871 871
872 872 if fnmatch.fnmatch(view_name, entry) and token_match:
873 873 auth_token_access_valid = True
874 874 break
875 875
876 876 if auth_token_access_valid:
877 877 log.debug('view: `%s` matches entry in whitelist: %s'
878 878 % (view_name, whitelist))
879 879 else:
880 880 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
881 881 % (view_name, whitelist))
882 882 if auth_token:
883 883 # if we use auth token key and don't have access it's a warning
884 884 log.warning(msg)
885 885 else:
886 886 log.debug(msg)
887 887
888 888 return auth_token_access_valid
889 889
890 890
891 891 class AuthUser(object):
892 892 """
893 893 A simple object that handles all attributes of user in RhodeCode
894 894
895 895 It does lookup based on API key,given user, or user present in session
896 896 Then it fills all required information for such user. It also checks if
897 897 anonymous access is enabled and if so, it returns default user as logged in
898 898 """
899 899 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
900 900
901 901 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
902 902
903 903 self.user_id = user_id
904 904 self._api_key = api_key
905 905
906 906 self.api_key = None
907 907 self.feed_token = ''
908 908 self.username = username
909 909 self.ip_addr = ip_addr
910 910 self.name = ''
911 911 self.lastname = ''
912 912 self.first_name = ''
913 913 self.last_name = ''
914 914 self.email = ''
915 915 self.is_authenticated = False
916 916 self.admin = False
917 917 self.inherit_default_permissions = False
918 918 self.password = ''
919 919
920 920 self.anonymous_user = None # propagated on propagate_data
921 921 self.propagate_data()
922 922 self._instance = None
923 923 self._permissions_scoped_cache = {} # used to bind scoped calculation
924 924
925 925 @LazyProperty
926 926 def permissions(self):
927 927 return self.get_perms(user=self, cache=False)
928 928
929 929 @LazyProperty
930 930 def permissions_full_details(self):
931 931 return self.get_perms(
932 932 user=self, cache=False, calculate_super_admin=True)
933 933
934 934 def permissions_with_scope(self, scope):
935 935 """
936 936 Call the get_perms function with scoped data. The scope in that function
937 937 narrows the SQL calls to the given ID of objects resulting in fetching
938 938 Just particular permission we want to obtain. If scope is an empty dict
939 939 then it basically narrows the scope to GLOBAL permissions only.
940 940
941 941 :param scope: dict
942 942 """
943 943 if 'repo_name' in scope:
944 944 obj = Repository.get_by_repo_name(scope['repo_name'])
945 945 if obj:
946 946 scope['repo_id'] = obj.repo_id
947 947 _scope = {
948 948 'repo_id': -1,
949 949 'user_group_id': -1,
950 950 'repo_group_id': -1,
951 951 }
952 952 _scope.update(scope)
953 953 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
954 954 _scope.items())))
955 955 if cache_key not in self._permissions_scoped_cache:
956 956 # store in cache to mimic how the @LazyProperty works,
957 957 # the difference here is that we use the unique key calculated
958 958 # from params and values
959 959 res = self.get_perms(user=self, cache=False, scope=_scope)
960 960 self._permissions_scoped_cache[cache_key] = res
961 961 return self._permissions_scoped_cache[cache_key]
962 962
963 963 def get_instance(self):
964 964 return User.get(self.user_id)
965 965
966 966 def update_lastactivity(self):
967 967 if self.user_id:
968 968 User.get(self.user_id).update_lastactivity()
969 969
970 970 def propagate_data(self):
971 971 """
972 972 Fills in user data and propagates values to this instance. Maps fetched
973 973 user attributes to this class instance attributes
974 974 """
975 975 log.debug('AuthUser: starting data propagation for new potential user')
976 976 user_model = UserModel()
977 977 anon_user = self.anonymous_user = User.get_default_user(cache=True)
978 978 is_user_loaded = False
979 979
980 980 # lookup by userid
981 981 if self.user_id is not None and self.user_id != anon_user.user_id:
982 982 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
983 983 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
984 984
985 985 # try go get user by api key
986 986 elif self._api_key and self._api_key != anon_user.api_key:
987 987 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
988 988 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
989 989
990 990 # lookup by username
991 991 elif self.username:
992 992 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
993 993 is_user_loaded = user_model.fill_data(self, username=self.username)
994 994 else:
995 995 log.debug('No data in %s that could been used to log in', self)
996 996
997 997 if not is_user_loaded:
998 998 log.debug('Failed to load user. Fallback to default user')
999 999 # if we cannot authenticate user try anonymous
1000 1000 if anon_user.active:
1001 1001 user_model.fill_data(self, user_id=anon_user.user_id)
1002 1002 # then we set this user is logged in
1003 1003 self.is_authenticated = True
1004 1004 else:
1005 1005 # in case of disabled anonymous user we reset some of the
1006 1006 # parameters so such user is "corrupted", skipping the fill_data
1007 1007 for attr in ['user_id', 'username', 'admin', 'active']:
1008 1008 setattr(self, attr, None)
1009 1009 self.is_authenticated = False
1010 1010
1011 1011 if not self.username:
1012 1012 self.username = 'None'
1013 1013
1014 1014 log.debug('AuthUser: propagated user is now %s', self)
1015 1015
1016 1016 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1017 1017 calculate_super_admin=False, cache=False):
1018 1018 """
1019 1019 Fills user permission attribute with permissions taken from database
1020 1020 works for permissions given for repositories, and for permissions that
1021 1021 are granted to groups
1022 1022
1023 1023 :param user: instance of User object from database
1024 1024 :param explicit: In case there are permissions both for user and a group
1025 1025 that user is part of, explicit flag will defiine if user will
1026 1026 explicitly override permissions from group, if it's False it will
1027 1027 make decision based on the algo
1028 1028 :param algo: algorithm to decide what permission should be choose if
1029 1029 it's multiple defined, eg user in two different groups. It also
1030 1030 decides if explicit flag is turned off how to specify the permission
1031 1031 for case when user is in a group + have defined separate permission
1032 1032 """
1033 1033 user_id = user.user_id
1034 1034 user_is_admin = user.is_admin
1035 1035
1036 1036 # inheritance of global permissions like create repo/fork repo etc
1037 1037 user_inherit_default_permissions = user.inherit_default_permissions
1038 1038
1039 1039 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
1040 1040 compute = caches.conditional_cache(
1041 1041 'short_term', 'cache_desc',
1042 1042 condition=cache, func=_cached_perms_data)
1043 1043 result = compute(user_id, scope, user_is_admin,
1044 1044 user_inherit_default_permissions, explicit, algo,
1045 1045 calculate_super_admin)
1046 1046
1047 1047 result_repr = []
1048 1048 for k in result:
1049 1049 result_repr.append((k, len(result[k])))
1050 1050
1051 1051 log.debug('PERMISSION tree computed %s' % (result_repr,))
1052 1052 return result
1053 1053
1054 1054 @property
1055 1055 def is_default(self):
1056 1056 return self.username == User.DEFAULT_USER
1057 1057
1058 1058 @property
1059 1059 def is_admin(self):
1060 1060 return self.admin
1061 1061
1062 1062 @property
1063 1063 def is_user_object(self):
1064 1064 return self.user_id is not None
1065 1065
1066 1066 @property
1067 1067 def repositories_admin(self):
1068 1068 """
1069 1069 Returns list of repositories you're an admin of
1070 1070 """
1071 1071 return [
1072 1072 x[0] for x in self.permissions['repositories'].iteritems()
1073 1073 if x[1] == 'repository.admin']
1074 1074
1075 1075 @property
1076 1076 def repository_groups_admin(self):
1077 1077 """
1078 1078 Returns list of repository groups you're an admin of
1079 1079 """
1080 1080 return [
1081 1081 x[0] for x in self.permissions['repositories_groups'].iteritems()
1082 1082 if x[1] == 'group.admin']
1083 1083
1084 1084 @property
1085 1085 def user_groups_admin(self):
1086 1086 """
1087 1087 Returns list of user groups you're an admin of
1088 1088 """
1089 1089 return [
1090 1090 x[0] for x in self.permissions['user_groups'].iteritems()
1091 1091 if x[1] == 'usergroup.admin']
1092 1092
1093 1093 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1094 1094 """
1095 1095 Returns list of repository ids that user have access to based on given
1096 1096 perms. The cache flag should be only used in cases that are used for
1097 1097 display purposes, NOT IN ANY CASE for permission checks.
1098 1098 """
1099 1099 from rhodecode.model.scm import RepoList
1100 1100 if not perms:
1101 1101 perms = [
1102 1102 'repository.read', 'repository.write', 'repository.admin']
1103 1103
1104 1104 def _cached_repo_acl(user_id, perm_def, name_filter):
1105 1105 qry = Repository.query()
1106 1106 if name_filter:
1107 1107 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1108 1108 qry = qry.filter(
1109 1109 Repository.repo_name.ilike(ilike_expression))
1110 1110
1111 1111 return [x.repo_id for x in
1112 1112 RepoList(qry, perm_set=perm_def)]
1113 1113
1114 1114 compute = caches.conditional_cache(
1115 1115 'long_term', 'repo_acl_ids',
1116 1116 condition=cache, func=_cached_repo_acl)
1117 1117 return compute(self.user_id, perms, name_filter)
1118 1118
1119 1119 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1120 1120 """
1121 1121 Returns list of repository group ids that user have access to based on given
1122 1122 perms. The cache flag should be only used in cases that are used for
1123 1123 display purposes, NOT IN ANY CASE for permission checks.
1124 1124 """
1125 1125 from rhodecode.model.scm import RepoGroupList
1126 1126 if not perms:
1127 1127 perms = [
1128 1128 'group.read', 'group.write', 'group.admin']
1129 1129
1130 1130 def _cached_repo_group_acl(user_id, perm_def, name_filter):
1131 1131 qry = RepoGroup.query()
1132 1132 if name_filter:
1133 1133 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1134 1134 qry = qry.filter(
1135 1135 RepoGroup.group_name.ilike(ilike_expression))
1136 1136
1137 1137 return [x.group_id for x in
1138 1138 RepoGroupList(qry, perm_set=perm_def)]
1139 1139
1140 1140 compute = caches.conditional_cache(
1141 1141 'long_term', 'repo_group_acl_ids',
1142 1142 condition=cache, func=_cached_repo_group_acl)
1143 1143 return compute(self.user_id, perms, name_filter)
1144 1144
1145 1145 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1146 1146 """
1147 1147 Returns list of user group ids that user have access to based on given
1148 1148 perms. The cache flag should be only used in cases that are used for
1149 1149 display purposes, NOT IN ANY CASE for permission checks.
1150 1150 """
1151 1151 from rhodecode.model.scm import UserGroupList
1152 1152 if not perms:
1153 1153 perms = [
1154 1154 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1155 1155
1156 1156 def _cached_user_group_acl(user_id, perm_def, name_filter):
1157 1157 qry = UserGroup.query()
1158 1158 if name_filter:
1159 1159 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1160 1160 qry = qry.filter(
1161 1161 UserGroup.users_group_name.ilike(ilike_expression))
1162 1162
1163 1163 return [x.users_group_id for x in
1164 1164 UserGroupList(qry, perm_set=perm_def)]
1165 1165
1166 1166 compute = caches.conditional_cache(
1167 1167 'long_term', 'user_group_acl_ids',
1168 1168 condition=cache, func=_cached_user_group_acl)
1169 1169 return compute(self.user_id, perms, name_filter)
1170 1170
1171 1171 @property
1172 1172 def ip_allowed(self):
1173 1173 """
1174 1174 Checks if ip_addr used in constructor is allowed from defined list of
1175 1175 allowed ip_addresses for user
1176 1176
1177 1177 :returns: boolean, True if ip is in allowed ip range
1178 1178 """
1179 1179 # check IP
1180 1180 inherit = self.inherit_default_permissions
1181 1181 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1182 1182 inherit_from_default=inherit)
1183 1183 @property
1184 1184 def personal_repo_group(self):
1185 1185 return RepoGroup.get_user_personal_repo_group(self.user_id)
1186 1186
1187 1187 @classmethod
1188 1188 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1189 1189 allowed_ips = AuthUser.get_allowed_ips(
1190 1190 user_id, cache=True, inherit_from_default=inherit_from_default)
1191 1191 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1192 1192 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
1193 1193 return True
1194 1194 else:
1195 1195 log.info('Access for IP:%s forbidden, '
1196 1196 'not in %s' % (ip_addr, allowed_ips))
1197 1197 return False
1198 1198
1199 1199 def __repr__(self):
1200 1200 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1201 1201 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1202 1202
1203 1203 def set_authenticated(self, authenticated=True):
1204 1204 if self.user_id != self.anonymous_user.user_id:
1205 1205 self.is_authenticated = authenticated
1206 1206
1207 1207 def get_cookie_store(self):
1208 1208 return {
1209 1209 'username': self.username,
1210 1210 'password': md5(self.password),
1211 1211 'user_id': self.user_id,
1212 1212 'is_authenticated': self.is_authenticated
1213 1213 }
1214 1214
1215 1215 @classmethod
1216 1216 def from_cookie_store(cls, cookie_store):
1217 1217 """
1218 1218 Creates AuthUser from a cookie store
1219 1219
1220 1220 :param cls:
1221 1221 :param cookie_store:
1222 1222 """
1223 1223 user_id = cookie_store.get('user_id')
1224 1224 username = cookie_store.get('username')
1225 1225 api_key = cookie_store.get('api_key')
1226 1226 return AuthUser(user_id, api_key, username)
1227 1227
1228 1228 @classmethod
1229 1229 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1230 1230 _set = set()
1231 1231
1232 1232 if inherit_from_default:
1233 1233 default_ips = UserIpMap.query().filter(
1234 1234 UserIpMap.user == User.get_default_user(cache=True))
1235 1235 if cache:
1236 1236 default_ips = default_ips.options(
1237 1237 FromCache("sql_cache_short", "get_user_ips_default"))
1238 1238
1239 1239 # populate from default user
1240 1240 for ip in default_ips:
1241 1241 try:
1242 1242 _set.add(ip.ip_addr)
1243 1243 except ObjectDeletedError:
1244 1244 # since we use heavy caching sometimes it happens that
1245 1245 # we get deleted objects here, we just skip them
1246 1246 pass
1247 1247
1248 1248 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1249 1249 if cache:
1250 1250 user_ips = user_ips.options(
1251 1251 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1252 1252
1253 1253 for ip in user_ips:
1254 1254 try:
1255 1255 _set.add(ip.ip_addr)
1256 1256 except ObjectDeletedError:
1257 1257 # since we use heavy caching sometimes it happens that we get
1258 1258 # deleted objects here, we just skip them
1259 1259 pass
1260 1260 return _set or set(['0.0.0.0/0', '::/0'])
1261 1261
1262 1262
1263 1263 def set_available_permissions(config):
1264 1264 """
1265 1265 This function will propagate pylons globals with all available defined
1266 1266 permission given in db. We don't want to check each time from db for new
1267 1267 permissions since adding a new permission also requires application restart
1268 1268 ie. to decorate new views with the newly created permission
1269 1269
1270 1270 :param config: current pylons config instance
1271 1271
1272 1272 """
1273 1273 log.info('getting information about all available permissions')
1274 1274 try:
1275 1275 sa = meta.Session
1276 1276 all_perms = sa.query(Permission).all()
1277 1277 config['available_permissions'] = [x.permission_name for x in all_perms]
1278 1278 except Exception:
1279 1279 log.error(traceback.format_exc())
1280 1280 finally:
1281 1281 meta.Session.remove()
1282 1282
1283 1283
1284 1284 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1285 1285 """
1286 1286 Return the current authentication token, creating one if one doesn't
1287 1287 already exist and the save_if_missing flag is present.
1288 1288
1289 1289 :param session: pass in the pylons session, else we use the global ones
1290 1290 :param force_new: force to re-generate the token and store it in session
1291 1291 :param save_if_missing: save the newly generated token if it's missing in
1292 1292 session
1293 1293 """
1294 1294 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1295 1295 # from pyramid.csrf import get_csrf_token
1296 1296
1297 1297 if not session:
1298 1298 from pylons import session
1299 1299
1300 1300 if (csrf_token_key not in session and save_if_missing) or force_new:
1301 1301 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1302 1302 session[csrf_token_key] = token
1303 1303 if hasattr(session, 'save'):
1304 1304 session.save()
1305 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 1309 from pyramid.threadlocal import get_current_request
1310 1310 pyramid_request = get_current_request()
1311 1311 if not pyramid_request:
1312 1312 # return global request of pylons in case pyramid isn't available
1313 1313 # NOTE(marcink): this should be removed after migration to pyramid
1314 1314 from pylons import request
1315 1315 return request
1316 1316 return pyramid_request
1317 1317
1318 1318
1319 1319 # CHECK DECORATORS
1320 1320 class CSRFRequired(object):
1321 1321 """
1322 1322 Decorator for authenticating a form
1323 1323
1324 1324 This decorator uses an authorization token stored in the client's
1325 1325 session for prevention of certain Cross-site request forgery (CSRF)
1326 1326 attacks (See
1327 1327 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1328 1328 information).
1329 1329
1330 1330 For use with the ``webhelpers.secure_form`` helper functions.
1331 1331
1332 1332 """
1333 1333 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1334 1334 except_methods=None):
1335 1335 self.token = token
1336 1336 self.header = header
1337 1337 self.except_methods = except_methods or []
1338 1338
1339 1339 def __call__(self, func):
1340 1340 return get_cython_compat_decorator(self.__wrapper, func)
1341 1341
1342 1342 def _get_csrf(self, _request):
1343 1343 return _request.POST.get(self.token, _request.headers.get(self.header))
1344 1344
1345 1345 def check_csrf(self, _request, cur_token):
1346 1346 supplied_token = self._get_csrf(_request)
1347 1347 return supplied_token and supplied_token == cur_token
1348 1348
1349 1349 def _get_request(self):
1350 1350 return get_request(self)
1351 1351
1352 1352 def __wrapper(self, func, *fargs, **fkwargs):
1353 1353 request = self._get_request()
1354 1354
1355 1355 if request.method in self.except_methods:
1356 1356 return func(*fargs, **fkwargs)
1357 1357
1358 1358 cur_token = get_csrf_token(save_if_missing=False)
1359 1359 if self.check_csrf(request, cur_token):
1360 1360 if request.POST.get(self.token):
1361 1361 del request.POST[self.token]
1362 1362 return func(*fargs, **fkwargs)
1363 1363 else:
1364 1364 reason = 'token-missing'
1365 1365 supplied_token = self._get_csrf(request)
1366 1366 if supplied_token and cur_token != supplied_token:
1367 1367 reason = 'token-mismatch [%s:%s]' % (
1368 1368 cur_token or ''[:6], supplied_token or ''[:6])
1369 1369
1370 1370 csrf_message = \
1371 1371 ("Cross-site request forgery detected, request denied. See "
1372 1372 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1373 1373 "more information.")
1374 1374 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1375 1375 'REMOTE_ADDR:%s, HEADERS:%s' % (
1376 1376 request, reason, request.remote_addr, request.headers))
1377 1377
1378 1378 raise HTTPForbidden(explanation=csrf_message)
1379 1379
1380 1380
1381 1381 class LoginRequired(object):
1382 1382 """
1383 1383 Must be logged in to execute this function else
1384 1384 redirect to login page
1385 1385
1386 1386 :param api_access: if enabled this checks only for valid auth token
1387 1387 and grants access based on valid token
1388 1388 """
1389 1389 def __init__(self, auth_token_access=None):
1390 1390 self.auth_token_access = auth_token_access
1391 1391
1392 1392 def __call__(self, func):
1393 1393 return get_cython_compat_decorator(self.__wrapper, func)
1394 1394
1395 1395 def _get_request(self):
1396 1396 return get_request(self)
1397 1397
1398 1398 def __wrapper(self, func, *fargs, **fkwargs):
1399 1399 from rhodecode.lib import helpers as h
1400 1400 cls = fargs[0]
1401 1401 user = cls._rhodecode_user
1402 1402 request = self._get_request()
1403 1403
1404 1404 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1405 1405 log.debug('Starting login restriction checks for user: %s' % (user,))
1406 1406 # check if our IP is allowed
1407 1407 ip_access_valid = True
1408 1408 if not user.ip_allowed:
1409 1409 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1410 1410 category='warning')
1411 1411 ip_access_valid = False
1412 1412
1413 1413 # check if we used an APIKEY and it's a valid one
1414 1414 # defined white-list of controllers which API access will be enabled
1415 1415 _auth_token = request.GET.get(
1416 1416 'auth_token', '') or request.GET.get('api_key', '')
1417 1417 auth_token_access_valid = allowed_auth_token_access(
1418 1418 loc, auth_token=_auth_token)
1419 1419
1420 1420 # explicit controller is enabled or API is in our whitelist
1421 1421 if self.auth_token_access or auth_token_access_valid:
1422 1422 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1423 1423 db_user = user.get_instance()
1424 1424
1425 1425 if db_user:
1426 1426 if self.auth_token_access:
1427 1427 roles = self.auth_token_access
1428 1428 else:
1429 1429 roles = [UserApiKeys.ROLE_HTTP]
1430 1430 token_match = db_user.authenticate_by_token(
1431 1431 _auth_token, roles=roles)
1432 1432 else:
1433 1433 log.debug('Unable to fetch db instance for auth user: %s', user)
1434 1434 token_match = False
1435 1435
1436 1436 if _auth_token and token_match:
1437 1437 auth_token_access_valid = True
1438 1438 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1439 1439 else:
1440 1440 auth_token_access_valid = False
1441 1441 if not _auth_token:
1442 1442 log.debug("AUTH TOKEN *NOT* present in request")
1443 1443 else:
1444 1444 log.warning(
1445 1445 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1446 1446
1447 1447 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1448 1448 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1449 1449 else 'AUTH_TOKEN_AUTH'
1450 1450
1451 1451 if ip_access_valid and (
1452 1452 user.is_authenticated or auth_token_access_valid):
1453 1453 log.info(
1454 1454 'user %s authenticating with:%s IS authenticated on func %s'
1455 1455 % (user, reason, loc))
1456 1456
1457 1457 # update user data to check last activity
1458 1458 user.update_lastactivity()
1459 1459 Session().commit()
1460 1460 return func(*fargs, **fkwargs)
1461 1461 else:
1462 1462 log.warning(
1463 1463 'user %s authenticating with:%s NOT authenticated on '
1464 1464 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1465 1465 % (user, reason, loc, ip_access_valid,
1466 1466 auth_token_access_valid))
1467 1467 # we preserve the get PARAM
1468 1468 came_from = request.path_qs
1469 1469 log.debug('redirecting to login page with %s' % (came_from,))
1470 1470 raise HTTPFound(
1471 1471 h.route_path('login', _query={'came_from': came_from}))
1472 1472
1473 1473
1474 1474 class NotAnonymous(object):
1475 1475 """
1476 1476 Must be logged in to execute this function else
1477 1477 redirect to login page
1478 1478 """
1479 1479
1480 1480 def __call__(self, func):
1481 1481 return get_cython_compat_decorator(self.__wrapper, func)
1482 1482
1483 1483 def _get_request(self):
1484 1484 return get_request(self)
1485 1485
1486 1486 def __wrapper(self, func, *fargs, **fkwargs):
1487 1487 import rhodecode.lib.helpers as h
1488 1488 cls = fargs[0]
1489 1489 self.user = cls._rhodecode_user
1490 1490 request = self._get_request()
1491 1491
1492 1492 log.debug('Checking if user is not anonymous @%s' % cls)
1493 1493
1494 1494 anonymous = self.user.username == User.DEFAULT_USER
1495 1495
1496 1496 if anonymous:
1497 1497 came_from = request.path_qs
1498 1498 h.flash(_('You need to be a registered user to '
1499 1499 'perform this action'),
1500 1500 category='warning')
1501 1501 raise HTTPFound(
1502 1502 h.route_path('login', _query={'came_from': came_from}))
1503 1503 else:
1504 1504 return func(*fargs, **fkwargs)
1505 1505
1506 1506
1507 1507 class PermsDecorator(object):
1508 1508 """
1509 1509 Base class for controller decorators, we extract the current user from
1510 1510 the class itself, which has it stored in base controllers
1511 1511 """
1512 1512
1513 1513 def __init__(self, *required_perms):
1514 1514 self.required_perms = set(required_perms)
1515 1515
1516 1516 def __call__(self, func):
1517 1517 return get_cython_compat_decorator(self.__wrapper, func)
1518 1518
1519 1519 def _get_request(self):
1520 1520 return get_request(self)
1521 1521
1522 1522 def _get_came_from(self):
1523 1523 _request = self._get_request()
1524 1524
1525 1525 # both pylons/pyramid has this attribute
1526 1526 return _request.path_qs
1527 1527
1528 1528 def __wrapper(self, func, *fargs, **fkwargs):
1529 1529 import rhodecode.lib.helpers as h
1530 1530 cls = fargs[0]
1531 1531 _user = cls._rhodecode_user
1532 1532
1533 1533 log.debug('checking %s permissions %s for %s %s',
1534 1534 self.__class__.__name__, self.required_perms, cls, _user)
1535 1535
1536 1536 if self.check_permissions(_user):
1537 1537 log.debug('Permission granted for %s %s', cls, _user)
1538 1538 return func(*fargs, **fkwargs)
1539 1539
1540 1540 else:
1541 1541 log.debug('Permission denied for %s %s', cls, _user)
1542 1542 anonymous = _user.username == User.DEFAULT_USER
1543 1543
1544 1544 if anonymous:
1545 1545 came_from = self._get_came_from()
1546 1546 h.flash(_('You need to be signed in to view this page'),
1547 1547 category='warning')
1548 1548 raise HTTPFound(
1549 1549 h.route_path('login', _query={'came_from': came_from}))
1550 1550
1551 1551 else:
1552 1552 # redirect with 404 to prevent resource discovery
1553 1553 raise HTTPNotFound()
1554 1554
1555 1555 def check_permissions(self, user):
1556 1556 """Dummy function for overriding"""
1557 1557 raise NotImplementedError(
1558 1558 'You have to write this function in child class')
1559 1559
1560 1560
1561 1561 class HasPermissionAllDecorator(PermsDecorator):
1562 1562 """
1563 1563 Checks for access permission for all given predicates. All of them
1564 1564 have to be meet in order to fulfill the request
1565 1565 """
1566 1566
1567 1567 def check_permissions(self, user):
1568 1568 perms = user.permissions_with_scope({})
1569 1569 if self.required_perms.issubset(perms['global']):
1570 1570 return True
1571 1571 return False
1572 1572
1573 1573
1574 1574 class HasPermissionAnyDecorator(PermsDecorator):
1575 1575 """
1576 1576 Checks for access permission for any of given predicates. In order to
1577 1577 fulfill the request any of predicates must be meet
1578 1578 """
1579 1579
1580 1580 def check_permissions(self, user):
1581 1581 perms = user.permissions_with_scope({})
1582 1582 if self.required_perms.intersection(perms['global']):
1583 1583 return True
1584 1584 return False
1585 1585
1586 1586
1587 1587 class HasRepoPermissionAllDecorator(PermsDecorator):
1588 1588 """
1589 1589 Checks for access permission for all given predicates for specific
1590 1590 repository. All of them have to be meet in order to fulfill the request
1591 1591 """
1592 1592 def _get_repo_name(self):
1593 1593 _request = self._get_request()
1594 1594 return get_repo_slug(_request)
1595 1595
1596 1596 def check_permissions(self, user):
1597 1597 perms = user.permissions
1598 1598 repo_name = self._get_repo_name()
1599 1599
1600 1600 try:
1601 1601 user_perms = set([perms['repositories'][repo_name]])
1602 1602 except KeyError:
1603 1603 log.debug('cannot locate repo with name: `%s` in permissions defs',
1604 1604 repo_name)
1605 1605 return False
1606 1606
1607 1607 log.debug('checking `%s` permissions for repo `%s`',
1608 1608 user_perms, repo_name)
1609 1609 if self.required_perms.issubset(user_perms):
1610 1610 return True
1611 1611 return False
1612 1612
1613 1613
1614 1614 class HasRepoPermissionAnyDecorator(PermsDecorator):
1615 1615 """
1616 1616 Checks for access permission for any of given predicates for specific
1617 1617 repository. In order to fulfill the request any of predicates must be meet
1618 1618 """
1619 1619 def _get_repo_name(self):
1620 1620 _request = self._get_request()
1621 1621 return get_repo_slug(_request)
1622 1622
1623 1623 def check_permissions(self, user):
1624 1624 perms = user.permissions
1625 1625 repo_name = self._get_repo_name()
1626 1626
1627 1627 try:
1628 1628 user_perms = set([perms['repositories'][repo_name]])
1629 1629 except KeyError:
1630 1630 log.debug(
1631 1631 'cannot locate repo with name: `%s` in permissions defs',
1632 1632 repo_name)
1633 1633 return False
1634 1634
1635 1635 log.debug('checking `%s` permissions for repo `%s`',
1636 1636 user_perms, repo_name)
1637 1637 if self.required_perms.intersection(user_perms):
1638 1638 return True
1639 1639 return False
1640 1640
1641 1641
1642 1642 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1643 1643 """
1644 1644 Checks for access permission for all given predicates for specific
1645 1645 repository group. All of them have to be meet in order to
1646 1646 fulfill the request
1647 1647 """
1648 1648 def _get_repo_group_name(self):
1649 1649 _request = self._get_request()
1650 1650 return get_repo_group_slug(_request)
1651 1651
1652 1652 def check_permissions(self, user):
1653 1653 perms = user.permissions
1654 1654 group_name = self._get_repo_group_name()
1655 1655 try:
1656 1656 user_perms = set([perms['repositories_groups'][group_name]])
1657 1657 except KeyError:
1658 1658 log.debug(
1659 1659 'cannot locate repo group with name: `%s` in permissions defs',
1660 1660 group_name)
1661 1661 return False
1662 1662
1663 1663 log.debug('checking `%s` permissions for repo group `%s`',
1664 1664 user_perms, group_name)
1665 1665 if self.required_perms.issubset(user_perms):
1666 1666 return True
1667 1667 return False
1668 1668
1669 1669
1670 1670 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1671 1671 """
1672 1672 Checks for access permission for any of given predicates for specific
1673 1673 repository group. In order to fulfill the request any
1674 1674 of predicates must be met
1675 1675 """
1676 1676 def _get_repo_group_name(self):
1677 1677 _request = self._get_request()
1678 1678 return get_repo_group_slug(_request)
1679 1679
1680 1680 def check_permissions(self, user):
1681 1681 perms = user.permissions
1682 1682 group_name = self._get_repo_group_name()
1683 1683
1684 1684 try:
1685 1685 user_perms = set([perms['repositories_groups'][group_name]])
1686 1686 except KeyError:
1687 1687 log.debug(
1688 1688 'cannot locate repo group with name: `%s` in permissions defs',
1689 1689 group_name)
1690 1690 return False
1691 1691
1692 1692 log.debug('checking `%s` permissions for repo group `%s`',
1693 1693 user_perms, group_name)
1694 1694 if self.required_perms.intersection(user_perms):
1695 1695 return True
1696 1696 return False
1697 1697
1698 1698
1699 1699 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1700 1700 """
1701 1701 Checks for access permission for all given predicates for specific
1702 1702 user group. All of them have to be meet in order to fulfill the request
1703 1703 """
1704 1704 def _get_user_group_name(self):
1705 1705 _request = self._get_request()
1706 1706 return get_user_group_slug(_request)
1707 1707
1708 1708 def check_permissions(self, user):
1709 1709 perms = user.permissions
1710 1710 group_name = self._get_user_group_name()
1711 1711 try:
1712 1712 user_perms = set([perms['user_groups'][group_name]])
1713 1713 except KeyError:
1714 1714 return False
1715 1715
1716 1716 if self.required_perms.issubset(user_perms):
1717 1717 return True
1718 1718 return False
1719 1719
1720 1720
1721 1721 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1722 1722 """
1723 1723 Checks for access permission for any of given predicates for specific
1724 1724 user group. In order to fulfill the request any of predicates must be meet
1725 1725 """
1726 1726 def _get_user_group_name(self):
1727 1727 _request = self._get_request()
1728 1728 return get_user_group_slug(_request)
1729 1729
1730 1730 def check_permissions(self, user):
1731 1731 perms = user.permissions
1732 1732 group_name = self._get_user_group_name()
1733 1733 try:
1734 1734 user_perms = set([perms['user_groups'][group_name]])
1735 1735 except KeyError:
1736 1736 return False
1737 1737
1738 1738 if self.required_perms.intersection(user_perms):
1739 1739 return True
1740 1740 return False
1741 1741
1742 1742
1743 1743 # CHECK FUNCTIONS
1744 1744 class PermsFunction(object):
1745 1745 """Base function for other check functions"""
1746 1746
1747 1747 def __init__(self, *perms):
1748 1748 self.required_perms = set(perms)
1749 1749 self.repo_name = None
1750 1750 self.repo_group_name = None
1751 1751 self.user_group_name = None
1752 1752
1753 1753 def __bool__(self):
1754 1754 frame = inspect.currentframe()
1755 1755 stack_trace = traceback.format_stack(frame)
1756 1756 log.error('Checking bool value on a class instance of perm '
1757 1757 'function is not allowed: %s' % ''.join(stack_trace))
1758 1758 # rather than throwing errors, here we always return False so if by
1759 1759 # accident someone checks truth for just an instance it will always end
1760 1760 # up in returning False
1761 1761 return False
1762 1762 __nonzero__ = __bool__
1763 1763
1764 1764 def __call__(self, check_location='', user=None):
1765 1765 if not user:
1766 1766 log.debug('Using user attribute from global request')
1767 1767 # TODO: remove this someday,put as user as attribute here
1768 1768 request = self._get_request()
1769 1769 user = request.user
1770 1770
1771 1771 # init auth user if not already given
1772 1772 if not isinstance(user, AuthUser):
1773 1773 log.debug('Wrapping user %s into AuthUser', user)
1774 1774 user = AuthUser(user.user_id)
1775 1775
1776 1776 cls_name = self.__class__.__name__
1777 1777 check_scope = self._get_check_scope(cls_name)
1778 1778 check_location = check_location or 'unspecified location'
1779 1779
1780 1780 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1781 1781 self.required_perms, user, check_scope, check_location)
1782 1782 if not user:
1783 1783 log.warning('Empty user given for permission check')
1784 1784 return False
1785 1785
1786 1786 if self.check_permissions(user):
1787 1787 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1788 1788 check_scope, user, check_location)
1789 1789 return True
1790 1790
1791 1791 else:
1792 1792 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1793 1793 check_scope, user, check_location)
1794 1794 return False
1795 1795
1796 1796 def _get_request(self):
1797 1797 return get_request(self)
1798 1798
1799 1799 def _get_check_scope(self, cls_name):
1800 1800 return {
1801 1801 'HasPermissionAll': 'GLOBAL',
1802 1802 'HasPermissionAny': 'GLOBAL',
1803 1803 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1804 1804 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1805 1805 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1806 1806 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1807 1807 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1808 1808 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1809 1809 }.get(cls_name, '?:%s' % cls_name)
1810 1810
1811 1811 def check_permissions(self, user):
1812 1812 """Dummy function for overriding"""
1813 1813 raise Exception('You have to write this function in child class')
1814 1814
1815 1815
1816 1816 class HasPermissionAll(PermsFunction):
1817 1817 def check_permissions(self, user):
1818 1818 perms = user.permissions_with_scope({})
1819 1819 if self.required_perms.issubset(perms.get('global')):
1820 1820 return True
1821 1821 return False
1822 1822
1823 1823
1824 1824 class HasPermissionAny(PermsFunction):
1825 1825 def check_permissions(self, user):
1826 1826 perms = user.permissions_with_scope({})
1827 1827 if self.required_perms.intersection(perms.get('global')):
1828 1828 return True
1829 1829 return False
1830 1830
1831 1831
1832 1832 class HasRepoPermissionAll(PermsFunction):
1833 1833 def __call__(self, repo_name=None, check_location='', user=None):
1834 1834 self.repo_name = repo_name
1835 1835 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1836 1836
1837 1837 def _get_repo_name(self):
1838 1838 if not self.repo_name:
1839 1839 _request = self._get_request()
1840 1840 self.repo_name = get_repo_slug(_request)
1841 1841 return self.repo_name
1842 1842
1843 1843 def check_permissions(self, user):
1844 1844 self.repo_name = self._get_repo_name()
1845 1845 perms = user.permissions
1846 1846 try:
1847 1847 user_perms = set([perms['repositories'][self.repo_name]])
1848 1848 except KeyError:
1849 1849 return False
1850 1850 if self.required_perms.issubset(user_perms):
1851 1851 return True
1852 1852 return False
1853 1853
1854 1854
1855 1855 class HasRepoPermissionAny(PermsFunction):
1856 1856 def __call__(self, repo_name=None, check_location='', user=None):
1857 1857 self.repo_name = repo_name
1858 1858 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1859 1859
1860 1860 def _get_repo_name(self):
1861 1861 if not self.repo_name:
1862 1862 _request = self._get_request()
1863 1863 self.repo_name = get_repo_slug(_request)
1864 1864 return self.repo_name
1865 1865
1866 1866 def check_permissions(self, user):
1867 1867 self.repo_name = self._get_repo_name()
1868 1868 perms = user.permissions
1869 1869 try:
1870 1870 user_perms = set([perms['repositories'][self.repo_name]])
1871 1871 except KeyError:
1872 1872 return False
1873 1873 if self.required_perms.intersection(user_perms):
1874 1874 return True
1875 1875 return False
1876 1876
1877 1877
1878 1878 class HasRepoGroupPermissionAny(PermsFunction):
1879 1879 def __call__(self, group_name=None, check_location='', user=None):
1880 1880 self.repo_group_name = group_name
1881 1881 return super(HasRepoGroupPermissionAny, self).__call__(
1882 1882 check_location, user)
1883 1883
1884 1884 def check_permissions(self, user):
1885 1885 perms = user.permissions
1886 1886 try:
1887 1887 user_perms = set(
1888 1888 [perms['repositories_groups'][self.repo_group_name]])
1889 1889 except KeyError:
1890 1890 return False
1891 1891 if self.required_perms.intersection(user_perms):
1892 1892 return True
1893 1893 return False
1894 1894
1895 1895
1896 1896 class HasRepoGroupPermissionAll(PermsFunction):
1897 1897 def __call__(self, group_name=None, check_location='', user=None):
1898 1898 self.repo_group_name = group_name
1899 1899 return super(HasRepoGroupPermissionAll, self).__call__(
1900 1900 check_location, user)
1901 1901
1902 1902 def check_permissions(self, user):
1903 1903 perms = user.permissions
1904 1904 try:
1905 1905 user_perms = set(
1906 1906 [perms['repositories_groups'][self.repo_group_name]])
1907 1907 except KeyError:
1908 1908 return False
1909 1909 if self.required_perms.issubset(user_perms):
1910 1910 return True
1911 1911 return False
1912 1912
1913 1913
1914 1914 class HasUserGroupPermissionAny(PermsFunction):
1915 1915 def __call__(self, user_group_name=None, check_location='', user=None):
1916 1916 self.user_group_name = user_group_name
1917 1917 return super(HasUserGroupPermissionAny, self).__call__(
1918 1918 check_location, user)
1919 1919
1920 1920 def check_permissions(self, user):
1921 1921 perms = user.permissions
1922 1922 try:
1923 1923 user_perms = set([perms['user_groups'][self.user_group_name]])
1924 1924 except KeyError:
1925 1925 return False
1926 1926 if self.required_perms.intersection(user_perms):
1927 1927 return True
1928 1928 return False
1929 1929
1930 1930
1931 1931 class HasUserGroupPermissionAll(PermsFunction):
1932 1932 def __call__(self, user_group_name=None, check_location='', user=None):
1933 1933 self.user_group_name = user_group_name
1934 1934 return super(HasUserGroupPermissionAll, self).__call__(
1935 1935 check_location, user)
1936 1936
1937 1937 def check_permissions(self, user):
1938 1938 perms = user.permissions
1939 1939 try:
1940 1940 user_perms = set([perms['user_groups'][self.user_group_name]])
1941 1941 except KeyError:
1942 1942 return False
1943 1943 if self.required_perms.issubset(user_perms):
1944 1944 return True
1945 1945 return False
1946 1946
1947 1947
1948 1948 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1949 1949 class HasPermissionAnyMiddleware(object):
1950 1950 def __init__(self, *perms):
1951 1951 self.required_perms = set(perms)
1952 1952
1953 1953 def __call__(self, user, repo_name):
1954 1954 # repo_name MUST be unicode, since we handle keys in permission
1955 1955 # dict by unicode
1956 1956 repo_name = safe_unicode(repo_name)
1957 1957 user = AuthUser(user.user_id)
1958 1958 log.debug(
1959 1959 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1960 1960 self.required_perms, user, repo_name)
1961 1961
1962 1962 if self.check_permissions(user, repo_name):
1963 1963 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1964 1964 repo_name, user, 'PermissionMiddleware')
1965 1965 return True
1966 1966
1967 1967 else:
1968 1968 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1969 1969 repo_name, user, 'PermissionMiddleware')
1970 1970 return False
1971 1971
1972 1972 def check_permissions(self, user, repo_name):
1973 1973 perms = user.permissions_with_scope({'repo_name': repo_name})
1974 1974
1975 1975 try:
1976 1976 user_perms = set([perms['repositories'][repo_name]])
1977 1977 except Exception:
1978 1978 log.exception('Error while accessing user permissions')
1979 1979 return False
1980 1980
1981 1981 if self.required_perms.intersection(user_perms):
1982 1982 return True
1983 1983 return False
1984 1984
1985 1985
1986 1986 # SPECIAL VERSION TO HANDLE API AUTH
1987 1987 class _BaseApiPerm(object):
1988 1988 def __init__(self, *perms):
1989 1989 self.required_perms = set(perms)
1990 1990
1991 1991 def __call__(self, check_location=None, user=None, repo_name=None,
1992 1992 group_name=None, user_group_name=None):
1993 1993 cls_name = self.__class__.__name__
1994 1994 check_scope = 'global:%s' % (self.required_perms,)
1995 1995 if repo_name:
1996 1996 check_scope += ', repo_name:%s' % (repo_name,)
1997 1997
1998 1998 if group_name:
1999 1999 check_scope += ', repo_group_name:%s' % (group_name,)
2000 2000
2001 2001 if user_group_name:
2002 2002 check_scope += ', user_group_name:%s' % (user_group_name,)
2003 2003
2004 2004 log.debug(
2005 2005 'checking cls:%s %s %s @ %s'
2006 2006 % (cls_name, self.required_perms, check_scope, check_location))
2007 2007 if not user:
2008 2008 log.debug('Empty User passed into arguments')
2009 2009 return False
2010 2010
2011 2011 # process user
2012 2012 if not isinstance(user, AuthUser):
2013 2013 user = AuthUser(user.user_id)
2014 2014 if not check_location:
2015 2015 check_location = 'unspecified'
2016 2016 if self.check_permissions(user.permissions, repo_name, group_name,
2017 2017 user_group_name):
2018 2018 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2019 2019 check_scope, user, check_location)
2020 2020 return True
2021 2021
2022 2022 else:
2023 2023 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2024 2024 check_scope, user, check_location)
2025 2025 return False
2026 2026
2027 2027 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2028 2028 user_group_name=None):
2029 2029 """
2030 2030 implement in child class should return True if permissions are ok,
2031 2031 False otherwise
2032 2032
2033 2033 :param perm_defs: dict with permission definitions
2034 2034 :param repo_name: repo name
2035 2035 """
2036 2036 raise NotImplementedError()
2037 2037
2038 2038
2039 2039 class HasPermissionAllApi(_BaseApiPerm):
2040 2040 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2041 2041 user_group_name=None):
2042 2042 if self.required_perms.issubset(perm_defs.get('global')):
2043 2043 return True
2044 2044 return False
2045 2045
2046 2046
2047 2047 class HasPermissionAnyApi(_BaseApiPerm):
2048 2048 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2049 2049 user_group_name=None):
2050 2050 if self.required_perms.intersection(perm_defs.get('global')):
2051 2051 return True
2052 2052 return False
2053 2053
2054 2054
2055 2055 class HasRepoPermissionAllApi(_BaseApiPerm):
2056 2056 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2057 2057 user_group_name=None):
2058 2058 try:
2059 2059 _user_perms = set([perm_defs['repositories'][repo_name]])
2060 2060 except KeyError:
2061 2061 log.warning(traceback.format_exc())
2062 2062 return False
2063 2063 if self.required_perms.issubset(_user_perms):
2064 2064 return True
2065 2065 return False
2066 2066
2067 2067
2068 2068 class HasRepoPermissionAnyApi(_BaseApiPerm):
2069 2069 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2070 2070 user_group_name=None):
2071 2071 try:
2072 2072 _user_perms = set([perm_defs['repositories'][repo_name]])
2073 2073 except KeyError:
2074 2074 log.warning(traceback.format_exc())
2075 2075 return False
2076 2076 if self.required_perms.intersection(_user_perms):
2077 2077 return True
2078 2078 return False
2079 2079
2080 2080
2081 2081 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2082 2082 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2083 2083 user_group_name=None):
2084 2084 try:
2085 2085 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2086 2086 except KeyError:
2087 2087 log.warning(traceback.format_exc())
2088 2088 return False
2089 2089 if self.required_perms.intersection(_user_perms):
2090 2090 return True
2091 2091 return False
2092 2092
2093 2093
2094 2094 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2095 2095 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2096 2096 user_group_name=None):
2097 2097 try:
2098 2098 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2099 2099 except KeyError:
2100 2100 log.warning(traceback.format_exc())
2101 2101 return False
2102 2102 if self.required_perms.issubset(_user_perms):
2103 2103 return True
2104 2104 return False
2105 2105
2106 2106
2107 2107 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2108 2108 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2109 2109 user_group_name=None):
2110 2110 try:
2111 2111 _user_perms = set([perm_defs['user_groups'][user_group_name]])
2112 2112 except KeyError:
2113 2113 log.warning(traceback.format_exc())
2114 2114 return False
2115 2115 if self.required_perms.intersection(_user_perms):
2116 2116 return True
2117 2117 return False
2118 2118
2119 2119
2120 2120 def check_ip_access(source_ip, allowed_ips=None):
2121 2121 """
2122 2122 Checks if source_ip is a subnet of any of allowed_ips.
2123 2123
2124 2124 :param source_ip:
2125 2125 :param allowed_ips: list of allowed ips together with mask
2126 2126 """
2127 2127 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2128 2128 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2129 2129 if isinstance(allowed_ips, (tuple, list, set)):
2130 2130 for ip in allowed_ips:
2131 2131 ip = safe_unicode(ip)
2132 2132 try:
2133 2133 network_address = ipaddress.ip_network(ip, strict=False)
2134 2134 if source_ip_address in network_address:
2135 2135 log.debug('IP %s is network %s' %
2136 2136 (source_ip_address, network_address))
2137 2137 return True
2138 2138 # for any case we cannot determine the IP, don't crash just
2139 2139 # skip it and log as error, we want to say forbidden still when
2140 2140 # sending bad IP
2141 2141 except Exception:
2142 2142 log.error(traceback.format_exc())
2143 2143 continue
2144 2144 return False
2145 2145
2146 2146
2147 2147 def get_cython_compat_decorator(wrapper, func):
2148 2148 """
2149 2149 Creates a cython compatible decorator. The previously used
2150 2150 decorator.decorator() function seems to be incompatible with cython.
2151 2151
2152 2152 :param wrapper: __wrapper method of the decorator class
2153 2153 :param func: decorated function
2154 2154 """
2155 2155 @wraps(func)
2156 2156 def local_wrapper(*args, **kwds):
2157 2157 return wrapper(func, *args, **kwds)
2158 2158 local_wrapper.__wrapped__ = func
2159 2159 return local_wrapper
2160 2160
2161 2161
@@ -1,1054 +1,1053 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 from datetime import date
26 26
27 27 from sqlalchemy import *
28 28 from sqlalchemy.ext.hybrid import hybrid_property
29 29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 30 from beaker.cache import cache_region, region_invalidate
31 31
32 32 from rhodecode.lib.vcs import get_backend
33 33 from rhodecode.lib.vcs.utils.helpers import get_scm
34 34 from rhodecode.lib.vcs.exceptions import VCSError
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36 from rhodecode.lib.auth import generate_auth_token
37 37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
38 38 from rhodecode.lib.exceptions import UserGroupAssignedException
39 39 from rhodecode.lib.ext_json import json
40 40
41 41 from rhodecode.model.meta import Base, Session
42 42 from rhodecode.lib.caching_query import FromCache
43 43
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47 #==============================================================================
48 48 # BASE CLASSES
49 49 #==============================================================================
50 50
51 51 class ModelSerializer(json.JSONEncoder):
52 52 """
53 53 Simple Serializer for JSON,
54 54
55 55 usage::
56 56
57 57 to make object customized for serialization implement a __json__
58 58 method that will return a dict for serialization into json
59 59
60 60 example::
61 61
62 62 class Task(object):
63 63
64 64 def __init__(self, name, value):
65 65 self.name = name
66 66 self.value = value
67 67
68 68 def __json__(self):
69 69 return dict(name=self.name,
70 70 value=self.value)
71 71
72 72 """
73 73
74 74 def default(self, obj):
75 75
76 76 if hasattr(obj, '__json__'):
77 77 return obj.__json__()
78 78 else:
79 79 return json.JSONEncoder.default(self, obj)
80 80
81 81 class BaseModel(object):
82 82 """Base Model for all classess
83 83
84 84 """
85 85
86 86 @classmethod
87 87 def _get_keys(cls):
88 88 """return column names for this model """
89 89 return class_mapper(cls).c.keys()
90 90
91 91 def get_dict(self):
92 92 """return dict with keys and values corresponding
93 93 to this model data """
94 94
95 95 d = {}
96 96 for k in self._get_keys():
97 97 d[k] = getattr(self, k)
98 98 return d
99 99
100 100 def get_appstruct(self):
101 101 """return list with keys and values tupples corresponding
102 102 to this model data """
103 103
104 104 l = []
105 105 for k in self._get_keys():
106 106 l.append((k, getattr(self, k),))
107 107 return l
108 108
109 109 def populate_obj(self, populate_dict):
110 110 """populate model with data from given populate_dict"""
111 111
112 112 for k in self._get_keys():
113 113 if k in populate_dict:
114 114 setattr(self, k, populate_dict[k])
115 115
116 116 @classmethod
117 117 def query(cls):
118 118 return Session.query(cls)
119 119
120 120 @classmethod
121 121 def get(cls, id_):
122 122 if id_:
123 123 return cls.query().get(id_)
124 124
125 125 @classmethod
126 126 def getAll(cls):
127 127 return cls.query().all()
128 128
129 129 @classmethod
130 130 def delete(cls, id_):
131 131 obj = cls.query().get(id_)
132 132 Session.delete(obj)
133 133 Session.commit()
134 134
135 135
136 136 class RhodeCodeSetting(Base, BaseModel):
137 137 __tablename__ = 'rhodecode_settings'
138 138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
139 139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 140 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
141 141 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
142 142
143 143 def __init__(self, k='', v=''):
144 144 self.app_settings_name = k
145 145 self.app_settings_value = v
146 146
147 147
148 148 @validates('_app_settings_value')
149 149 def validate_settings_value(self, key, val):
150 150 assert type(val) == unicode
151 151 return val
152 152
153 153 @hybrid_property
154 154 def app_settings_value(self):
155 155 v = self._app_settings_value
156 156 if v == 'ldap_active':
157 157 v = str2bool(v)
158 158 return v
159 159
160 160 @app_settings_value.setter
161 161 def app_settings_value(self, val):
162 162 """
163 163 Setter that will always make sure we use unicode in app_settings_value
164 164
165 165 :param val:
166 166 """
167 167 self._app_settings_value = safe_unicode(val)
168 168
169 169 def __repr__(self):
170 170 return "<%s('%s:%s')>" % (self.__class__.__name__,
171 171 self.app_settings_name, self.app_settings_value)
172 172
173 173
174 174 @classmethod
175 175 def get_by_name(cls, ldap_key):
176 176 return cls.query()\
177 177 .filter(cls.app_settings_name == ldap_key).scalar()
178 178
179 179 @classmethod
180 180 def get_app_settings(cls, cache=False):
181 181
182 182 ret = cls.query()
183 183
184 184 if cache:
185 185 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
186 186
187 187 if not ret:
188 188 raise Exception('Could not get application settings !')
189 189 settings = {}
190 190 for each in ret:
191 191 settings['rhodecode_' + each.app_settings_name] = \
192 192 each.app_settings_value
193 193
194 194 return settings
195 195
196 196 @classmethod
197 197 def get_ldap_settings(cls, cache=False):
198 198 ret = cls.query()\
199 199 .filter(cls.app_settings_name.startswith('ldap_')).all()
200 200 fd = {}
201 201 for row in ret:
202 202 fd.update({row.app_settings_name:row.app_settings_value})
203 203
204 204 return fd
205 205
206 206
207 207 class RhodeCodeUi(Base, BaseModel):
208 208 __tablename__ = 'rhodecode_ui'
209 209 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
210 210
211 211 HOOK_REPO_SIZE = 'changegroup.repo_size'
212 212 HOOK_PUSH = 'pretxnchangegroup.push_logger'
213 213 HOOK_PULL = 'preoutgoing.pull_logger'
214 214
215 215 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
216 216 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
217 217 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
218 218 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
219 219 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
220 220
221 221
222 222 @classmethod
223 223 def get_by_key(cls, key):
224 224 return cls.query().filter(cls.ui_key == key)
225 225
226 226
227 227 @classmethod
228 228 def get_builtin_hooks(cls):
229 229 q = cls.query()
230 230 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
231 231 cls.HOOK_PUSH, cls.HOOK_PULL]))
232 232 return q.all()
233 233
234 234 @classmethod
235 235 def get_custom_hooks(cls):
236 236 q = cls.query()
237 237 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
238 238 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 239 q = q.filter(cls.ui_section == 'hooks')
240 240 return q.all()
241 241
242 242 @classmethod
243 243 def create_or_update_hook(cls, key, val):
244 244 new_ui = cls.get_by_key(key).scalar() or cls()
245 245 new_ui.ui_section = 'hooks'
246 246 new_ui.ui_active = True
247 247 new_ui.ui_key = key
248 248 new_ui.ui_value = val
249 249
250 250 Session.add(new_ui)
251 251 Session.commit()
252 252
253 253
254 254 class User(Base, BaseModel):
255 255 __tablename__ = 'users'
256 256 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
257 257 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
258 258 username = Column("username", String(255), nullable=True, unique=None, default=None)
259 259 password = Column("password", String(255), nullable=True, unique=None, default=None)
260 260 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
261 261 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
262 262 name = Column("name", String(255), nullable=True, unique=None, default=None)
263 263 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
264 264 email = Column("email", String(255), nullable=True, unique=None, default=None)
265 265 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
266 266 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
267 267 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
268 268
269 269 user_log = relationship('UserLog', cascade='all')
270 270 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
271 271
272 272 repositories = relationship('Repository')
273 273 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
274 274 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
275 275
276 276 group_member = relationship('UserGroupMember', cascade='all')
277 277
278 278 @property
279 279 def full_contact(self):
280 280 return '%s %s <%s>' % (self.name, self.lastname, self.email)
281 281
282 282 @property
283 283 def short_contact(self):
284 284 return '%s %s' % (self.name, self.lastname)
285 285
286 286 @property
287 287 def is_admin(self):
288 288 return self.admin
289 289
290 290 def __repr__(self):
291 291 try:
292 292 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
293 293 self.user_id, self.username)
294 294 except:
295 295 return self.__class__.__name__
296 296
297 297 @classmethod
298 298 def get_by_username(cls, username, case_insensitive=False):
299 299 if case_insensitive:
300 300 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
301 301 else:
302 302 return Session.query(cls).filter(cls.username == username).scalar()
303 303
304 304 @classmethod
305 305 def get_by_auth_token(cls, auth_token):
306 306 return cls.query().filter(cls.api_key == auth_token).one()
307 307
308 308 def update_lastlogin(self):
309 309 """Update user lastlogin"""
310 310
311 311 self.last_login = datetime.datetime.now()
312 312 Session.add(self)
313 313 Session.commit()
314 314 log.debug('updated user %s lastlogin' % self.username)
315 315
316 316 @classmethod
317 317 def create(cls, form_data):
318 318 from rhodecode.lib.auth import get_crypt_password
319 319
320 320 try:
321 321 new_user = cls()
322 322 for k, v in form_data.items():
323 323 if k == 'password':
324 324 v = get_crypt_password(v)
325 325 setattr(new_user, k, v)
326 326
327 327 new_user.api_key = generate_auth_token(form_data['username'])
328 328 Session.add(new_user)
329 329 Session.commit()
330 330 return new_user
331 331 except:
332 332 log.error(traceback.format_exc())
333 333 Session.rollback()
334 334 raise
335 335
336 336 class UserLog(Base, BaseModel):
337 337 __tablename__ = 'user_logs'
338 338 __table_args__ = {'extend_existing':True}
339 339 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
340 340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
341 341 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
342 342 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
343 343 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
344 344 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
345 345 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
346 346
347 347 @property
348 348 def action_as_day(self):
349 349 return date(*self.action_date.timetuple()[:3])
350 350
351 351 user = relationship('User')
352 352 repository = relationship('Repository')
353 353
354 354
355 355 class UserGroup(Base, BaseModel):
356 356 __tablename__ = 'users_groups'
357 357 __table_args__ = {'extend_existing':True}
358 358
359 359 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
360 360 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
361 361 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
362 362
363 363 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
364 364
365 365 def __repr__(self):
366 366 return '<userGroup(%s)>' % (self.users_group_name)
367 367
368 368 @classmethod
369 369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
370 370 if case_insensitive:
371 371 gr = cls.query()\
372 372 .filter(cls.users_group_name.ilike(group_name))
373 373 else:
374 374 gr = cls.query()\
375 375 .filter(cls.users_group_name == group_name)
376 376 if cache:
377 377 gr = gr.options(FromCache("sql_cache_short",
378 378 "get_user_%s" % group_name))
379 379 return gr.scalar()
380 380
381
382 381 @classmethod
383 382 def get(cls, users_group_id, cache=False):
384 383 users_group = cls.query()
385 384 if cache:
386 385 users_group = users_group.options(FromCache("sql_cache_short",
387 386 "get_users_group_%s" % users_group_id))
388 387 return users_group.get(users_group_id)
389 388
390 389 @classmethod
391 390 def create(cls, form_data):
392 391 try:
393 new_users_group = cls()
392 new_user_group = cls()
394 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 397 Session.commit()
399 return new_users_group
398 return new_user_group
400 399 except:
401 400 log.error(traceback.format_exc())
402 401 Session.rollback()
403 402 raise
404 403
405 404 @classmethod
406 405 def update(cls, users_group_id, form_data):
407 406
408 407 try:
409 408 users_group = cls.get(users_group_id, cache=False)
410 409
411 410 for k, v in form_data.items():
412 411 if k == 'users_group_members':
413 412 users_group.members = []
414 413 Session.flush()
415 414 members_list = []
416 415 if v:
417 416 v = [v] if isinstance(v, basestring) else v
418 417 for u_id in set(v):
419 418 member = UserGroupMember(users_group_id, u_id)
420 419 members_list.append(member)
421 420 setattr(users_group, 'members', members_list)
422 421 setattr(users_group, k, v)
423 422
424 423 Session.add(users_group)
425 424 Session.commit()
426 425 except:
427 426 log.error(traceback.format_exc())
428 427 Session.rollback()
429 428 raise
430 429
431 430 @classmethod
432 431 def delete(cls, user_group_id):
433 432 try:
434 433
435 434 # check if this group is not assigned to repo
436 435 assigned_groups = UserGroupRepoToPerm.query()\
437 436 .filter(UserGroupRepoToPerm.users_group_id ==
438 437 user_group_id).all()
439 438
440 439 if assigned_groups:
441 440 raise UserGroupAssignedException(
442 441 'UserGroup assigned to %s' % assigned_groups)
443 442
444 443 users_group = cls.get(user_group_id, cache=False)
445 444 Session.delete(users_group)
446 445 Session.commit()
447 446 except:
448 447 log.error(traceback.format_exc())
449 448 Session.rollback()
450 449 raise
451 450
452 451 class UserGroupMember(Base, BaseModel):
453 452 __tablename__ = 'users_groups_members'
454 453 __table_args__ = {'extend_existing':True}
455 454
456 455 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
457 456 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
458 457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
459 458
460 459 user = relationship('User', lazy='joined')
461 460 users_group = relationship('UserGroup')
462 461
463 462 def __init__(self, gr_id='', u_id=''):
464 463 self.users_group_id = gr_id
465 464 self.user_id = u_id
466 465
467 466 @staticmethod
468 467 def add_user_to_group(group, user):
469 468 ugm = UserGroupMember()
470 469 ugm.users_group = group
471 470 ugm.user = user
472 471 Session.add(ugm)
473 472 Session.commit()
474 473 return ugm
475 474
476 475 class Repository(Base, BaseModel):
477 476 __tablename__ = 'repositories'
478 477 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
479 478
480 479 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 480 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
482 481 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
483 482 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
484 483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
485 484 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
486 485 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
487 486 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
488 487 description = Column("description", String(10000), nullable=True, unique=None, default=None)
489 488 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
490 489
491 490 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
492 491 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
493 492
494 493
495 494 user = relationship('User')
496 495 fork = relationship('Repository', remote_side=repo_id)
497 496 group = relationship('RepoGroup')
498 497 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
499 498 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
500 499 stats = relationship('Statistics', cascade='all', uselist=False)
501 500
502 501 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
503 502
504 503 logs = relationship('UserLog', cascade='all')
505 504
506 505 def __repr__(self):
507 506 return "<%s('%s:%s')>" % (self.__class__.__name__,
508 507 self.repo_id, self.repo_name)
509 508
510 509 @classmethod
511 510 def url_sep(cls):
512 511 return '/'
513 512
514 513 @classmethod
515 514 def get_by_repo_name(cls, repo_name):
516 515 q = Session.query(cls).filter(cls.repo_name == repo_name)
517 516 q = q.options(joinedload(Repository.fork))\
518 517 .options(joinedload(Repository.user))\
519 518 .options(joinedload(Repository.group))
520 519 return q.one()
521 520
522 521 @classmethod
523 522 def get_repo_forks(cls, repo_id):
524 523 return cls.query().filter(Repository.fork_id == repo_id)
525 524
526 525 @classmethod
527 526 def base_path(cls):
528 527 """
529 528 Returns base path when all repos are stored
530 529
531 530 :param cls:
532 531 """
533 532 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
534 533 cls.url_sep())
535 534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
536 535 return q.one().ui_value
537 536
538 537 @property
539 538 def just_name(self):
540 539 return self.repo_name.split(Repository.url_sep())[-1]
541 540
542 541 @property
543 542 def groups_with_parents(self):
544 543 groups = []
545 544 if self.group is None:
546 545 return groups
547 546
548 547 cur_gr = self.group
549 548 groups.insert(0, cur_gr)
550 549 while 1:
551 550 gr = getattr(cur_gr, 'parent_group', None)
552 551 cur_gr = cur_gr.parent_group
553 552 if gr is None:
554 553 break
555 554 groups.insert(0, gr)
556 555
557 556 return groups
558 557
559 558 @property
560 559 def groups_and_repo(self):
561 560 return self.groups_with_parents, self.just_name
562 561
563 562 @LazyProperty
564 563 def repo_path(self):
565 564 """
566 565 Returns base full path for that repository means where it actually
567 566 exists on a filesystem
568 567 """
569 568 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
570 569 Repository.url_sep())
571 570 q.options(FromCache("sql_cache_short", "repository_repo_path"))
572 571 return q.one().ui_value
573 572
574 573 @property
575 574 def repo_full_path(self):
576 575 p = [self.repo_path]
577 576 # we need to split the name by / since this is how we store the
578 577 # names in the database, but that eventually needs to be converted
579 578 # into a valid system path
580 579 p += self.repo_name.split(Repository.url_sep())
581 580 return os.path.join(*p)
582 581
583 582 def get_new_name(self, repo_name):
584 583 """
585 584 returns new full repository name based on assigned group and new new
586 585
587 586 :param group_name:
588 587 """
589 588 path_prefix = self.group.full_path_splitted if self.group else []
590 589 return Repository.url_sep().join(path_prefix + [repo_name])
591 590
592 591 @property
593 592 def _config(self):
594 593 """
595 594 Returns db based config object.
596 595 """
597 596 from rhodecode.lib.utils import make_db_config
598 597 return make_db_config(clear_session=False)
599 598
600 599 @classmethod
601 600 def is_valid(cls, repo_name):
602 601 """
603 602 returns True if given repo name is a valid filesystem repository
604 603
605 604 :param cls:
606 605 :param repo_name:
607 606 """
608 607 from rhodecode.lib.utils import is_valid_repo
609 608
610 609 return is_valid_repo(repo_name, cls.base_path())
611 610
612 611
613 612 #==========================================================================
614 613 # SCM PROPERTIES
615 614 #==========================================================================
616 615
617 616 def get_commit(self, rev):
618 617 return get_commit_safe(self.scm_instance, rev)
619 618
620 619 @property
621 620 def tip(self):
622 621 return self.get_commit('tip')
623 622
624 623 @property
625 624 def author(self):
626 625 return self.tip.author
627 626
628 627 @property
629 628 def last_change(self):
630 629 return self.scm_instance.last_change
631 630
632 631 #==========================================================================
633 632 # SCM CACHE INSTANCE
634 633 #==========================================================================
635 634
636 635 @property
637 636 def invalidate(self):
638 637 return CacheInvalidation.invalidate(self.repo_name)
639 638
640 639 def set_invalidate(self):
641 640 """
642 641 set a cache for invalidation for this instance
643 642 """
644 643 CacheInvalidation.set_invalidate(self.repo_name)
645 644
646 645 @LazyProperty
647 646 def scm_instance(self):
648 647 return self.__get_instance()
649 648
650 649 @property
651 650 def scm_instance_cached(self):
652 651 @cache_region('long_term')
653 652 def _c(repo_name):
654 653 return self.__get_instance()
655 654 rn = self.repo_name
656 655
657 656 inv = self.invalidate
658 657 if inv is not None:
659 658 region_invalidate(_c, None, rn)
660 659 # update our cache
661 660 CacheInvalidation.set_valid(inv.cache_key)
662 661 return _c(rn)
663 662
664 663 def __get_instance(self):
665 664
666 665 repo_full_path = self.repo_full_path
667 666
668 667 try:
669 668 alias = get_scm(repo_full_path)[0]
670 669 log.debug('Creating instance of %s repository' % alias)
671 670 backend = get_backend(alias)
672 671 except VCSError:
673 672 log.error(traceback.format_exc())
674 673 log.error('Perhaps this repository is in db and not in '
675 674 'filesystem run rescan repositories with '
676 675 '"destroy old data " option from admin panel')
677 676 return
678 677
679 678 if alias == 'hg':
680 679
681 680 repo = backend(safe_str(repo_full_path), create=False,
682 681 config=self._config)
683 682
684 683 else:
685 684 repo = backend(repo_full_path, create=False)
686 685
687 686 return repo
688 687
689 688
690 689 class Group(Base, BaseModel):
691 690 __tablename__ = 'groups'
692 691 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
693 692 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
694 693 __mapper_args__ = {'order_by':'group_name'}
695 694
696 695 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
697 696 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
698 697 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
699 698 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
700 699
701 700 parent_group = relationship('Group', remote_side=group_id)
702 701
703 702 def __init__(self, group_name='', parent_group=None):
704 703 self.group_name = group_name
705 704 self.parent_group = parent_group
706 705
707 706 def __repr__(self):
708 707 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
709 708 self.group_name)
710 709
711 710 @classmethod
712 711 def url_sep(cls):
713 712 return '/'
714 713
715 714 @classmethod
716 715 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
717 716 if case_insensitive:
718 717 gr = cls.query()\
719 718 .filter(cls.group_name.ilike(group_name))
720 719 else:
721 720 gr = cls.query()\
722 721 .filter(cls.group_name == group_name)
723 722 if cache:
724 723 gr = gr.options(FromCache("sql_cache_short",
725 724 "get_group_%s" % group_name))
726 725 return gr.scalar()
727 726
728 727 @property
729 728 def parents(self):
730 729 parents_recursion_limit = 5
731 730 groups = []
732 731 if self.parent_group is None:
733 732 return groups
734 733 cur_gr = self.parent_group
735 734 groups.insert(0, cur_gr)
736 735 cnt = 0
737 736 while 1:
738 737 cnt += 1
739 738 gr = getattr(cur_gr, 'parent_group', None)
740 739 cur_gr = cur_gr.parent_group
741 740 if gr is None:
742 741 break
743 742 if cnt == parents_recursion_limit:
744 743 # this will prevent accidental infinit loops
745 744 log.error('group nested more than %s' %
746 745 parents_recursion_limit)
747 746 break
748 747
749 748 groups.insert(0, gr)
750 749 return groups
751 750
752 751 @property
753 752 def children(self):
754 753 return Group.query().filter(Group.parent_group == self)
755 754
756 755 @property
757 756 def name(self):
758 757 return self.group_name.split(Group.url_sep())[-1]
759 758
760 759 @property
761 760 def full_path(self):
762 761 return self.group_name
763 762
764 763 @property
765 764 def full_path_splitted(self):
766 765 return self.group_name.split(Group.url_sep())
767 766
768 767 @property
769 768 def repositories(self):
770 769 return Repository.query().filter(Repository.group == self)
771 770
772 771 @property
773 772 def repositories_recursive_count(self):
774 773 cnt = self.repositories.count()
775 774
776 775 def children_count(group):
777 776 cnt = 0
778 777 for child in group.children:
779 778 cnt += child.repositories.count()
780 779 cnt += children_count(child)
781 780 return cnt
782 781
783 782 return cnt + children_count(self)
784 783
785 784
786 785 def get_new_name(self, group_name):
787 786 """
788 787 returns new full group name based on parent and new name
789 788
790 789 :param group_name:
791 790 """
792 791 path_prefix = (self.parent_group.full_path_splitted if
793 792 self.parent_group else [])
794 793 return Group.url_sep().join(path_prefix + [group_name])
795 794
796 795
797 796 class Permission(Base, BaseModel):
798 797 __tablename__ = 'permissions'
799 798 __table_args__ = {'extend_existing':True}
800 799 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
801 800 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
802 801 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
803 802
804 803 def __repr__(self):
805 804 return "<%s('%s:%s')>" % (self.__class__.__name__,
806 805 self.permission_id, self.permission_name)
807 806
808 807 @classmethod
809 808 def get_by_key(cls, key):
810 809 return cls.query().filter(cls.permission_name == key).scalar()
811 810
812 811 class UserRepoToPerm(Base, BaseModel):
813 812 __tablename__ = 'repo_to_perm'
814 813 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
815 814 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
816 815 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
817 816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 817 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
819 818
820 819 user = relationship('User')
821 820 permission = relationship('Permission')
822 821 repository = relationship('Repository')
823 822
824 823 class UserToPerm(Base, BaseModel):
825 824 __tablename__ = 'user_to_perm'
826 825 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
827 826 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
828 827 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
829 828 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
830 829
831 830 user = relationship('User')
832 831 permission = relationship('Permission')
833 832
834 833 @classmethod
835 834 def has_perm(cls, user_id, perm):
836 835 if not isinstance(perm, Permission):
837 836 raise Exception('perm needs to be an instance of Permission class')
838 837
839 838 return cls.query().filter(cls.user_id == user_id)\
840 839 .filter(cls.permission == perm).scalar() is not None
841 840
842 841 @classmethod
843 842 def grant_perm(cls, user_id, perm):
844 843 if not isinstance(perm, Permission):
845 844 raise Exception('perm needs to be an instance of Permission class')
846 845
847 846 new = cls()
848 847 new.user_id = user_id
849 848 new.permission = perm
850 849 try:
851 850 Session.add(new)
852 851 Session.commit()
853 852 except:
854 853 Session.rollback()
855 854
856 855
857 856 @classmethod
858 857 def revoke_perm(cls, user_id, perm):
859 858 if not isinstance(perm, Permission):
860 859 raise Exception('perm needs to be an instance of Permission class')
861 860
862 861 try:
863 862 cls.query().filter(cls.user_id == user_id) \
864 863 .filter(cls.permission == perm).delete()
865 864 Session.commit()
866 865 except:
867 866 Session.rollback()
868 867
869 868 class UserGroupRepoToPerm(Base, BaseModel):
870 869 __tablename__ = 'users_group_repo_to_perm'
871 870 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
872 871 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
873 872 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
874 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
875 874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
876 875
877 876 users_group = relationship('UserGroup')
878 877 permission = relationship('Permission')
879 878 repository = relationship('Repository')
880 879
881 880 def __repr__(self):
882 881 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
883 882
884 883 class UserGroupToPerm(Base, BaseModel):
885 884 __tablename__ = 'users_group_to_perm'
886 885 __table_args__ = {'extend_existing':True}
887 886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
888 887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
889 888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
890 889
891 890 users_group = relationship('UserGroup')
892 891 permission = relationship('Permission')
893 892
894 893
895 894 @classmethod
896 895 def has_perm(cls, users_group_id, perm):
897 896 if not isinstance(perm, Permission):
898 897 raise Exception('perm needs to be an instance of Permission class')
899 898
900 899 return cls.query().filter(cls.users_group_id ==
901 900 users_group_id)\
902 901 .filter(cls.permission == perm)\
903 902 .scalar() is not None
904 903
905 904 @classmethod
906 905 def grant_perm(cls, users_group_id, perm):
907 906 if not isinstance(perm, Permission):
908 907 raise Exception('perm needs to be an instance of Permission class')
909 908
910 909 new = cls()
911 910 new.users_group_id = users_group_id
912 911 new.permission = perm
913 912 try:
914 913 Session.add(new)
915 914 Session.commit()
916 915 except:
917 916 Session.rollback()
918 917
919 918
920 919 @classmethod
921 920 def revoke_perm(cls, users_group_id, perm):
922 921 if not isinstance(perm, Permission):
923 922 raise Exception('perm needs to be an instance of Permission class')
924 923
925 924 try:
926 925 cls.query().filter(cls.users_group_id == users_group_id) \
927 926 .filter(cls.permission == perm).delete()
928 927 Session.commit()
929 928 except:
930 929 Session.rollback()
931 930
932 931
933 932 class UserRepoGroupToPerm(Base, BaseModel):
934 933 __tablename__ = 'group_to_perm'
935 934 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
936 935
937 936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
939 938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940 939 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
941 940
942 941 user = relationship('User')
943 942 permission = relationship('Permission')
944 943 group = relationship('RepoGroup')
945 944
946 945 class Statistics(Base, BaseModel):
947 946 __tablename__ = 'statistics'
948 947 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
949 948 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 949 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
951 950 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
952 951 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
953 952 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
954 953 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
955 954
956 955 repository = relationship('Repository', single_parent=True)
957 956
958 957 class UserFollowing(Base, BaseModel):
959 958 __tablename__ = 'user_followings'
960 959 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
961 960 UniqueConstraint('user_id', 'follows_user_id')
962 961 , {'extend_existing':True})
963 962
964 963 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
965 964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
966 965 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
967 966 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 967 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
969 968
970 969 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
971 970
972 971 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
973 972 follows_repository = relationship('Repository', order_by='Repository.repo_name')
974 973
975 974
976 975 @classmethod
977 976 def get_repo_followers(cls, repo_id):
978 977 return cls.query().filter(cls.follows_repo_id == repo_id)
979 978
980 979 class CacheInvalidation(Base, BaseModel):
981 980 __tablename__ = 'cache_invalidation'
982 981 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
983 982 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 983 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
985 984 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
986 985 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
987 986
988 987
989 988 def __init__(self, cache_key, cache_args=''):
990 989 self.cache_key = cache_key
991 990 self.cache_args = cache_args
992 991 self.cache_active = False
993 992
994 993 def __repr__(self):
995 994 return "<%s('%s:%s')>" % (self.__class__.__name__,
996 995 self.cache_id, self.cache_key)
997 996
998 997 @classmethod
999 998 def invalidate(cls, key):
1000 999 """
1001 1000 Returns Invalidation object if this given key should be invalidated
1002 1001 None otherwise. `cache_active = False` means that this cache
1003 1002 state is not valid and needs to be invalidated
1004 1003
1005 1004 :param key:
1006 1005 """
1007 1006 return cls.query()\
1008 1007 .filter(CacheInvalidation.cache_key == key)\
1009 1008 .filter(CacheInvalidation.cache_active == False)\
1010 1009 .scalar()
1011 1010
1012 1011 @classmethod
1013 1012 def set_invalidate(cls, key):
1014 1013 """
1015 1014 Mark this Cache key for invalidation
1016 1015
1017 1016 :param key:
1018 1017 """
1019 1018
1020 1019 log.debug('marking %s for invalidation' % key)
1021 1020 inv_obj = Session.query(cls)\
1022 1021 .filter(cls.cache_key == key).scalar()
1023 1022 if inv_obj:
1024 1023 inv_obj.cache_active = False
1025 1024 else:
1026 1025 log.debug('cache key not found in invalidation db -> creating one')
1027 1026 inv_obj = CacheInvalidation(key)
1028 1027
1029 1028 try:
1030 1029 Session.add(inv_obj)
1031 1030 Session.commit()
1032 1031 except Exception:
1033 1032 log.error(traceback.format_exc())
1034 1033 Session.rollback()
1035 1034
1036 1035 @classmethod
1037 1036 def set_valid(cls, key):
1038 1037 """
1039 1038 Mark this cache key as active and currently cached
1040 1039
1041 1040 :param key:
1042 1041 """
1043 1042 inv_obj = Session.query(CacheInvalidation)\
1044 1043 .filter(CacheInvalidation.cache_key == key).scalar()
1045 1044 inv_obj.cache_active = True
1046 1045 Session.add(inv_obj)
1047 1046 Session.commit()
1048 1047
1049 1048 class DbMigrateVersion(Base, BaseModel):
1050 1049 __tablename__ = 'db_migrate_version'
1051 1050 __table_args__ = {'extend_existing':True}
1052 1051 repository_id = Column('repository_id', String(250), primary_key=True)
1053 1052 repository_path = Column('repository_path', Text)
1054 1053 version = Column('version', Integer)
@@ -1,973 +1,980 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import shutil
32 32 import tempfile
33 33 import traceback
34 34 import tarfile
35 35 import warnings
36 36 import hashlib
37 37 from os.path import join as jn
38 38
39 39 import paste
40 40 import pkg_resources
41 41 from paste.script.command import Command, BadCommand
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43 from mako import exceptions
44 44 from pyramid.threadlocal import get_current_registry
45 45 from pyramid.request import Request
46 46
47 47 from rhodecode.lib.fakemod import create_module
48 48 from rhodecode.lib.vcs.backends.base import Config
49 49 from rhodecode.lib.vcs.exceptions import VCSError
50 50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 51 from rhodecode.lib.utils2 import (
52 52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 53 from rhodecode.model import meta
54 54 from rhodecode.model.db import (
55 55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 56 from rhodecode.model.meta import Session
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62 62
63 63 # String which contains characters that are not allowed in slug names for
64 64 # repositories or repository groups. It is properly escaped to use it in
65 65 # regular expressions.
66 66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67 67
68 68 # Regex that matches forbidden characters in repo/group slugs.
69 69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70 70
71 71 # Regex that matches allowed characters in repo/group slugs.
72 72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73 73
74 74 # Regex that matches whole repo/group slugs.
75 75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76 76
77 77 _license_cache = None
78 78
79 79
80 80 def repo_name_slug(value):
81 81 """
82 82 Return slug of name of repository
83 83 This function is called on each creation/modification
84 84 of repository to prevent bad names in repo
85 85 """
86 86 replacement_char = '-'
87 87
88 88 slug = remove_formatting(value)
89 89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 90 slug = re.sub('[\s]+', '-', slug)
91 91 slug = collapse(slug, replacement_char)
92 92 return slug
93 93
94 94
95 95 #==============================================================================
96 96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 97 #==============================================================================
98 98 def get_repo_slug(request):
99 99 _repo = ''
100 100 if isinstance(request, Request):
101 101 if hasattr(request, 'db_repo'):
102 102 # if our requests has set db reference use it for name, this
103 103 # translates the example.com/_<id> into proper repo names
104 104 _repo = request.db_repo.repo_name
105 105 elif getattr(request, 'matchdict', None):
106 106 # pyramid
107 107 _repo = request.matchdict.get('repo_name')
108 108
109 109 # TODO(marcink): remove after pylons migration...
110 110 if not _repo:
111 111 _repo = request.environ['pylons.routes_dict'].get('repo_name')
112 112
113 113 if _repo:
114 114 _repo = _repo.rstrip('/')
115 115 return _repo
116 116
117 117
118 118 def get_repo_group_slug(request):
119 119 _group = ''
120 120 if isinstance(request, Request):
121 121 if hasattr(request, 'db_repo_group'):
122 122 # if our requests has set db reference use it for name, this
123 123 # translates the example.com/_<id> into proper repo group names
124 124 _group = request.db_repo_group.group_name
125 125 elif getattr(request, 'matchdict', None):
126 126 # pyramid
127 127 _group = request.matchdict.get('repo_group_name')
128 128
129 129 # TODO(marcink): remove after pylons migration...
130 130 if not _group:
131 131 _group = request.environ['pylons.routes_dict'].get('group_name')
132 132
133 133 if _group:
134 134 _group = _group.rstrip('/')
135 135 return _group
136 136
137 137
138 138 def get_user_group_slug(request):
139 if isinstance(request, Request) and getattr(request, 'matchdict', None):
140 # pyramid
141 _group = request.matchdict.get('user_group_id')
142 else:
143 _group = request.environ['pylons.routes_dict'].get('user_group_id')
139 _user_group = ''
140 if isinstance(request, Request):
141
142 if hasattr(request, 'db_user_group'):
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:
146 _group = UserGroup.get(_group)
147 if _group:
148 _group = _group.users_group_name
149 except Exception:
150 log.exception('Failed to get user group by id')
151 # catch all failures here
152 return None
148 try:
149 _user_group = UserGroup.get(_user_group)
150 if _user_group:
151 _user_group = _user_group.users_group_name
152 except Exception:
153 log.exception('Failed to get user group by id')
154 # catch all failures here
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 164 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
158 165 """
159 166 Scans given path for repos and return (name,(type,path)) tuple
160 167
161 168 :param path: path to scan for repositories
162 169 :param recursive: recursive search and return names with subdirs in front
163 170 """
164 171
165 172 # remove ending slash for better results
166 173 path = path.rstrip(os.sep)
167 174 log.debug('now scanning in %s location recursive:%s...', path, recursive)
168 175
169 176 def _get_repos(p):
170 177 dirpaths = _get_dirpaths(p)
171 178 if not _is_dir_writable(p):
172 179 log.warning('repo path without write access: %s', p)
173 180
174 181 for dirpath in dirpaths:
175 182 if os.path.isfile(os.path.join(p, dirpath)):
176 183 continue
177 184 cur_path = os.path.join(p, dirpath)
178 185
179 186 # skip removed repos
180 187 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
181 188 continue
182 189
183 190 #skip .<somethin> dirs
184 191 if dirpath.startswith('.'):
185 192 continue
186 193
187 194 try:
188 195 scm_info = get_scm(cur_path)
189 196 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
190 197 except VCSError:
191 198 if not recursive:
192 199 continue
193 200 #check if this dir containts other repos for recursive scan
194 201 rec_path = os.path.join(p, dirpath)
195 202 if os.path.isdir(rec_path):
196 203 for inner_scm in _get_repos(rec_path):
197 204 yield inner_scm
198 205
199 206 return _get_repos(path)
200 207
201 208
202 209 def _get_dirpaths(p):
203 210 try:
204 211 # OS-independable way of checking if we have at least read-only
205 212 # access or not.
206 213 dirpaths = os.listdir(p)
207 214 except OSError:
208 215 log.warning('ignoring repo path without read access: %s', p)
209 216 return []
210 217
211 218 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
212 219 # decode paths and suddenly returns unicode objects itself. The items it
213 220 # cannot decode are returned as strings and cause issues.
214 221 #
215 222 # Those paths are ignored here until a solid solution for path handling has
216 223 # been built.
217 224 expected_type = type(p)
218 225
219 226 def _has_correct_type(item):
220 227 if type(item) is not expected_type:
221 228 log.error(
222 229 u"Ignoring path %s since it cannot be decoded into unicode.",
223 230 # Using "repr" to make sure that we see the byte value in case
224 231 # of support.
225 232 repr(item))
226 233 return False
227 234 return True
228 235
229 236 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
230 237
231 238 return dirpaths
232 239
233 240
234 241 def _is_dir_writable(path):
235 242 """
236 243 Probe if `path` is writable.
237 244
238 245 Due to trouble on Cygwin / Windows, this is actually probing if it is
239 246 possible to create a file inside of `path`, stat does not produce reliable
240 247 results in this case.
241 248 """
242 249 try:
243 250 with tempfile.TemporaryFile(dir=path):
244 251 pass
245 252 except OSError:
246 253 return False
247 254 return True
248 255
249 256
250 257 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
251 258 """
252 259 Returns True if given path is a valid repository False otherwise.
253 260 If expect_scm param is given also, compare if given scm is the same
254 261 as expected from scm parameter. If explicit_scm is given don't try to
255 262 detect the scm, just use the given one to check if repo is valid
256 263
257 264 :param repo_name:
258 265 :param base_path:
259 266 :param expect_scm:
260 267 :param explicit_scm:
261 268
262 269 :return True: if given path is a valid repository
263 270 """
264 271 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
265 272 log.debug('Checking if `%s` is a valid path for repository. '
266 273 'Explicit type: %s', repo_name, explicit_scm)
267 274
268 275 try:
269 276 if explicit_scm:
270 277 detected_scms = [get_scm_backend(explicit_scm)]
271 278 else:
272 279 detected_scms = get_scm(full_path)
273 280
274 281 if expect_scm:
275 282 return detected_scms[0] == expect_scm
276 283 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
277 284 return True
278 285 except VCSError:
279 286 log.debug('path: %s is not a valid repo !', full_path)
280 287 return False
281 288
282 289
283 290 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
284 291 """
285 292 Returns True if given path is a repository group, False otherwise
286 293
287 294 :param repo_name:
288 295 :param base_path:
289 296 """
290 297 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
291 298 log.debug('Checking if `%s` is a valid path for repository group',
292 299 repo_group_name)
293 300
294 301 # check if it's not a repo
295 302 if is_valid_repo(repo_group_name, base_path):
296 303 log.debug('Repo called %s exist, it is not a valid '
297 304 'repo group' % repo_group_name)
298 305 return False
299 306
300 307 try:
301 308 # we need to check bare git repos at higher level
302 309 # since we might match branches/hooks/info/objects or possible
303 310 # other things inside bare git repo
304 311 scm_ = get_scm(os.path.dirname(full_path))
305 312 log.debug('path: %s is a vcs object:%s, not valid '
306 313 'repo group' % (full_path, scm_))
307 314 return False
308 315 except VCSError:
309 316 pass
310 317
311 318 # check if it's a valid path
312 319 if skip_path_check or os.path.isdir(full_path):
313 320 log.debug('path: %s is a valid repo group !', full_path)
314 321 return True
315 322
316 323 log.debug('path: %s is not a valid repo group !', full_path)
317 324 return False
318 325
319 326
320 327 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
321 328 while True:
322 329 ok = raw_input(prompt)
323 330 if ok.lower() in ('y', 'ye', 'yes'):
324 331 return True
325 332 if ok.lower() in ('n', 'no', 'nop', 'nope'):
326 333 return False
327 334 retries = retries - 1
328 335 if retries < 0:
329 336 raise IOError
330 337 print(complaint)
331 338
332 339 # propagated from mercurial documentation
333 340 ui_sections = [
334 341 'alias', 'auth',
335 342 'decode/encode', 'defaults',
336 343 'diff', 'email',
337 344 'extensions', 'format',
338 345 'merge-patterns', 'merge-tools',
339 346 'hooks', 'http_proxy',
340 347 'smtp', 'patch',
341 348 'paths', 'profiling',
342 349 'server', 'trusted',
343 350 'ui', 'web', ]
344 351
345 352
346 353 def config_data_from_db(clear_session=True, repo=None):
347 354 """
348 355 Read the configuration data from the database and return configuration
349 356 tuples.
350 357 """
351 358 from rhodecode.model.settings import VcsSettingsModel
352 359
353 360 config = []
354 361
355 362 sa = meta.Session()
356 363 settings_model = VcsSettingsModel(repo=repo, sa=sa)
357 364
358 365 ui_settings = settings_model.get_ui_settings()
359 366
360 367 for setting in ui_settings:
361 368 if setting.active:
362 369 log.debug(
363 370 'settings ui from db: [%s] %s=%s',
364 371 setting.section, setting.key, setting.value)
365 372 config.append((
366 373 safe_str(setting.section), safe_str(setting.key),
367 374 safe_str(setting.value)))
368 375 if setting.key == 'push_ssl':
369 376 # force set push_ssl requirement to False, rhodecode
370 377 # handles that
371 378 config.append((
372 379 safe_str(setting.section), safe_str(setting.key), False))
373 380 if clear_session:
374 381 meta.Session.remove()
375 382
376 383 # TODO: mikhail: probably it makes no sense to re-read hooks information.
377 384 # It's already there and activated/deactivated
378 385 skip_entries = []
379 386 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
380 387 if 'pull' not in enabled_hook_classes:
381 388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
382 389 if 'push' not in enabled_hook_classes:
383 390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
384 391 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
385 392 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
386 393
387 394 config = [entry for entry in config if entry[:2] not in skip_entries]
388 395
389 396 return config
390 397
391 398
392 399 def make_db_config(clear_session=True, repo=None):
393 400 """
394 401 Create a :class:`Config` instance based on the values in the database.
395 402 """
396 403 config = Config()
397 404 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
398 405 for section, option, value in config_data:
399 406 config.set(section, option, value)
400 407 return config
401 408
402 409
403 410 def get_enabled_hook_classes(ui_settings):
404 411 """
405 412 Return the enabled hook classes.
406 413
407 414 :param ui_settings: List of ui_settings as returned
408 415 by :meth:`VcsSettingsModel.get_ui_settings`
409 416
410 417 :return: a list with the enabled hook classes. The order is not guaranteed.
411 418 :rtype: list
412 419 """
413 420 enabled_hooks = []
414 421 active_hook_keys = [
415 422 key for section, key, value, active in ui_settings
416 423 if section == 'hooks' and active]
417 424
418 425 hook_names = {
419 426 RhodeCodeUi.HOOK_PUSH: 'push',
420 427 RhodeCodeUi.HOOK_PULL: 'pull',
421 428 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
422 429 }
423 430
424 431 for key in active_hook_keys:
425 432 hook = hook_names.get(key)
426 433 if hook:
427 434 enabled_hooks.append(hook)
428 435
429 436 return enabled_hooks
430 437
431 438
432 439 def set_rhodecode_config(config):
433 440 """
434 441 Updates pylons config with new settings from database
435 442
436 443 :param config:
437 444 """
438 445 from rhodecode.model.settings import SettingsModel
439 446 app_settings = SettingsModel().get_all_settings()
440 447
441 448 for k, v in app_settings.items():
442 449 config[k] = v
443 450
444 451
445 452 def get_rhodecode_realm():
446 453 """
447 454 Return the rhodecode realm from database.
448 455 """
449 456 from rhodecode.model.settings import SettingsModel
450 457 realm = SettingsModel().get_setting_by_name('realm')
451 458 return safe_str(realm.app_settings_value)
452 459
453 460
454 461 def get_rhodecode_base_path():
455 462 """
456 463 Returns the base path. The base path is the filesystem path which points
457 464 to the repository store.
458 465 """
459 466 from rhodecode.model.settings import SettingsModel
460 467 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
461 468 return safe_str(paths_ui.ui_value)
462 469
463 470
464 471 def map_groups(path):
465 472 """
466 473 Given a full path to a repository, create all nested groups that this
467 474 repo is inside. This function creates parent-child relationships between
468 475 groups and creates default perms for all new groups.
469 476
470 477 :param paths: full path to repository
471 478 """
472 479 from rhodecode.model.repo_group import RepoGroupModel
473 480 sa = meta.Session()
474 481 groups = path.split(Repository.NAME_SEP)
475 482 parent = None
476 483 group = None
477 484
478 485 # last element is repo in nested groups structure
479 486 groups = groups[:-1]
480 487 rgm = RepoGroupModel(sa)
481 488 owner = User.get_first_super_admin()
482 489 for lvl, group_name in enumerate(groups):
483 490 group_name = '/'.join(groups[:lvl] + [group_name])
484 491 group = RepoGroup.get_by_group_name(group_name)
485 492 desc = '%s group' % group_name
486 493
487 494 # skip folders that are now removed repos
488 495 if REMOVED_REPO_PAT.match(group_name):
489 496 break
490 497
491 498 if group is None:
492 499 log.debug('creating group level: %s group_name: %s',
493 500 lvl, group_name)
494 501 group = RepoGroup(group_name, parent)
495 502 group.group_description = desc
496 503 group.user = owner
497 504 sa.add(group)
498 505 perm_obj = rgm._create_default_perms(group)
499 506 sa.add(perm_obj)
500 507 sa.flush()
501 508
502 509 parent = group
503 510 return group
504 511
505 512
506 513 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
507 514 """
508 515 maps all repos given in initial_repo_list, non existing repositories
509 516 are created, if remove_obsolete is True it also checks for db entries
510 517 that are not in initial_repo_list and removes them.
511 518
512 519 :param initial_repo_list: list of repositories found by scanning methods
513 520 :param remove_obsolete: check for obsolete entries in database
514 521 """
515 522 from rhodecode.model.repo import RepoModel
516 523 from rhodecode.model.scm import ScmModel
517 524 from rhodecode.model.repo_group import RepoGroupModel
518 525 from rhodecode.model.settings import SettingsModel
519 526
520 527 sa = meta.Session()
521 528 repo_model = RepoModel()
522 529 user = User.get_first_super_admin()
523 530 added = []
524 531
525 532 # creation defaults
526 533 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
527 534 enable_statistics = defs.get('repo_enable_statistics')
528 535 enable_locking = defs.get('repo_enable_locking')
529 536 enable_downloads = defs.get('repo_enable_downloads')
530 537 private = defs.get('repo_private')
531 538
532 539 for name, repo in initial_repo_list.items():
533 540 group = map_groups(name)
534 541 unicode_name = safe_unicode(name)
535 542 db_repo = repo_model.get_by_repo_name(unicode_name)
536 543 # found repo that is on filesystem not in RhodeCode database
537 544 if not db_repo:
538 545 log.info('repository %s not found, creating now', name)
539 546 added.append(name)
540 547 desc = (repo.description
541 548 if repo.description != 'unknown'
542 549 else '%s repository' % name)
543 550
544 551 db_repo = repo_model._create_repo(
545 552 repo_name=name,
546 553 repo_type=repo.alias,
547 554 description=desc,
548 555 repo_group=getattr(group, 'group_id', None),
549 556 owner=user,
550 557 enable_locking=enable_locking,
551 558 enable_downloads=enable_downloads,
552 559 enable_statistics=enable_statistics,
553 560 private=private,
554 561 state=Repository.STATE_CREATED
555 562 )
556 563 sa.commit()
557 564 # we added that repo just now, and make sure we updated server info
558 565 if db_repo.repo_type == 'git':
559 566 git_repo = db_repo.scm_instance()
560 567 # update repository server-info
561 568 log.debug('Running update server info')
562 569 git_repo._update_server_info()
563 570
564 571 db_repo.update_commit_cache()
565 572
566 573 config = db_repo._config
567 574 config.set('extensions', 'largefiles', '')
568 575 ScmModel().install_hooks(
569 576 db_repo.scm_instance(config=config),
570 577 repo_type=db_repo.repo_type)
571 578
572 579 removed = []
573 580 if remove_obsolete:
574 581 # remove from database those repositories that are not in the filesystem
575 582 for repo in sa.query(Repository).all():
576 583 if repo.repo_name not in initial_repo_list.keys():
577 584 log.debug("Removing non-existing repository found in db `%s`",
578 585 repo.repo_name)
579 586 try:
580 587 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
581 588 sa.commit()
582 589 removed.append(repo.repo_name)
583 590 except Exception:
584 591 # don't hold further removals on error
585 592 log.error(traceback.format_exc())
586 593 sa.rollback()
587 594
588 595 def splitter(full_repo_name):
589 596 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
590 597 gr_name = None
591 598 if len(_parts) == 2:
592 599 gr_name = _parts[0]
593 600 return gr_name
594 601
595 602 initial_repo_group_list = [splitter(x) for x in
596 603 initial_repo_list.keys() if splitter(x)]
597 604
598 605 # remove from database those repository groups that are not in the
599 606 # filesystem due to parent child relationships we need to delete them
600 607 # in a specific order of most nested first
601 608 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
602 609 nested_sort = lambda gr: len(gr.split('/'))
603 610 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
604 611 if group_name not in initial_repo_group_list:
605 612 repo_group = RepoGroup.get_by_group_name(group_name)
606 613 if (repo_group.children.all() or
607 614 not RepoGroupModel().check_exist_filesystem(
608 615 group_name=group_name, exc_on_failure=False)):
609 616 continue
610 617
611 618 log.info(
612 619 'Removing non-existing repository group found in db `%s`',
613 620 group_name)
614 621 try:
615 622 RepoGroupModel(sa).delete(group_name, fs_remove=False)
616 623 sa.commit()
617 624 removed.append(group_name)
618 625 except Exception:
619 626 # don't hold further removals on error
620 627 log.exception(
621 628 'Unable to remove repository group `%s`',
622 629 group_name)
623 630 sa.rollback()
624 631 raise
625 632
626 633 return added, removed
627 634
628 635
629 636 def get_default_cache_settings(settings):
630 637 cache_settings = {}
631 638 for key in settings.keys():
632 639 for prefix in ['beaker.cache.', 'cache.']:
633 640 if key.startswith(prefix):
634 641 name = key.split(prefix)[1].strip()
635 642 cache_settings[name] = settings[key].strip()
636 643 return cache_settings
637 644
638 645
639 646 # set cache regions for beaker so celery can utilise it
640 647 def add_cache(settings):
641 648 from rhodecode.lib import caches
642 649 cache_settings = {'regions': None}
643 650 # main cache settings used as default ...
644 651 cache_settings.update(get_default_cache_settings(settings))
645 652
646 653 if cache_settings['regions']:
647 654 for region in cache_settings['regions'].split(','):
648 655 region = region.strip()
649 656 region_settings = {}
650 657 for key, value in cache_settings.items():
651 658 if key.startswith(region):
652 659 region_settings[key.split('.')[1]] = value
653 660
654 661 caches.configure_cache_region(
655 662 region, region_settings, cache_settings)
656 663
657 664
658 665 def load_rcextensions(root_path):
659 666 import rhodecode
660 667 from rhodecode.config import conf
661 668
662 669 path = os.path.join(root_path, 'rcextensions', '__init__.py')
663 670 if os.path.isfile(path):
664 671 rcext = create_module('rc', path)
665 672 EXT = rhodecode.EXTENSIONS = rcext
666 673 log.debug('Found rcextensions now loading %s...', rcext)
667 674
668 675 # Additional mappings that are not present in the pygments lexers
669 676 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
670 677
671 678 # auto check if the module is not missing any data, set to default if is
672 679 # this will help autoupdate new feature of rcext module
673 680 #from rhodecode.config import rcextensions
674 681 #for k in dir(rcextensions):
675 682 # if not k.startswith('_') and not hasattr(EXT, k):
676 683 # setattr(EXT, k, getattr(rcextensions, k))
677 684
678 685
679 686 def get_custom_lexer(extension):
680 687 """
681 688 returns a custom lexer if it is defined in rcextensions module, or None
682 689 if there's no custom lexer defined
683 690 """
684 691 import rhodecode
685 692 from pygments import lexers
686 693
687 694 # custom override made by RhodeCode
688 695 if extension in ['mako']:
689 696 return lexers.get_lexer_by_name('html+mako')
690 697
691 698 # check if we didn't define this extension as other lexer
692 699 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
693 700 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
694 701 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
695 702 return lexers.get_lexer_by_name(_lexer_name)
696 703
697 704
698 705 #==============================================================================
699 706 # TEST FUNCTIONS AND CREATORS
700 707 #==============================================================================
701 708 def create_test_index(repo_location, config):
702 709 """
703 710 Makes default test index.
704 711 """
705 712 import rc_testdata
706 713
707 714 rc_testdata.extract_search_index(
708 715 'vcs_search_index', os.path.dirname(config['search.location']))
709 716
710 717
711 718 def create_test_directory(test_path):
712 719 """
713 720 Create test directory if it doesn't exist.
714 721 """
715 722 if not os.path.isdir(test_path):
716 723 log.debug('Creating testdir %s', test_path)
717 724 os.makedirs(test_path)
718 725
719 726
720 727 def create_test_database(test_path, config):
721 728 """
722 729 Makes a fresh database.
723 730 """
724 731 from rhodecode.lib.db_manage import DbManage
725 732
726 733 # PART ONE create db
727 734 dbconf = config['sqlalchemy.db1.url']
728 735 log.debug('making test db %s', dbconf)
729 736
730 737 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
731 738 tests=True, cli_args={'force_ask': True})
732 739 dbmanage.create_tables(override=True)
733 740 dbmanage.set_db_version()
734 741 # for tests dynamically set new root paths based on generated content
735 742 dbmanage.create_settings(dbmanage.config_prompt(test_path))
736 743 dbmanage.create_default_user()
737 744 dbmanage.create_test_admin_and_users()
738 745 dbmanage.create_permissions()
739 746 dbmanage.populate_default_permissions()
740 747 Session().commit()
741 748
742 749
743 750 def create_test_repositories(test_path, config):
744 751 """
745 752 Creates test repositories in the temporary directory. Repositories are
746 753 extracted from archives within the rc_testdata package.
747 754 """
748 755 import rc_testdata
749 756 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
750 757
751 758 log.debug('making test vcs repositories')
752 759
753 760 idx_path = config['search.location']
754 761 data_path = config['cache_dir']
755 762
756 763 # clean index and data
757 764 if idx_path and os.path.exists(idx_path):
758 765 log.debug('remove %s', idx_path)
759 766 shutil.rmtree(idx_path)
760 767
761 768 if data_path and os.path.exists(data_path):
762 769 log.debug('remove %s', data_path)
763 770 shutil.rmtree(data_path)
764 771
765 772 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
766 773 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
767 774
768 775 # Note: Subversion is in the process of being integrated with the system,
769 776 # until we have a properly packed version of the test svn repository, this
770 777 # tries to copy over the repo from a package "rc_testdata"
771 778 svn_repo_path = rc_testdata.get_svn_repo_archive()
772 779 with tarfile.open(svn_repo_path) as tar:
773 780 tar.extractall(jn(test_path, SVN_REPO))
774 781
775 782
776 783 #==============================================================================
777 784 # PASTER COMMANDS
778 785 #==============================================================================
779 786 class BasePasterCommand(Command):
780 787 """
781 788 Abstract Base Class for paster commands.
782 789
783 790 The celery commands are somewhat aggressive about loading
784 791 celery.conf, and since our module sets the `CELERY_LOADER`
785 792 environment variable to our loader, we have to bootstrap a bit and
786 793 make sure we've had a chance to load the pylons config off of the
787 794 command line, otherwise everything fails.
788 795 """
789 796 min_args = 1
790 797 min_args_error = "Please provide a paster config file as an argument."
791 798 takes_config_file = 1
792 799 requires_config_file = True
793 800
794 801 def notify_msg(self, msg, log=False):
795 802 """Make a notification to user, additionally if logger is passed
796 803 it logs this action using given logger
797 804
798 805 :param msg: message that will be printed to user
799 806 :param log: logging instance, to use to additionally log this message
800 807
801 808 """
802 809 if log and isinstance(log, logging):
803 810 log(msg)
804 811
805 812 def run(self, args):
806 813 """
807 814 Overrides Command.run
808 815
809 816 Checks for a config file argument and loads it.
810 817 """
811 818 if len(args) < self.min_args:
812 819 raise BadCommand(
813 820 self.min_args_error % {'min_args': self.min_args,
814 821 'actual_args': len(args)})
815 822
816 823 # Decrement because we're going to lob off the first argument.
817 824 # @@ This is hacky
818 825 self.min_args -= 1
819 826 self.bootstrap_config(args[0])
820 827 self.update_parser()
821 828 return super(BasePasterCommand, self).run(args[1:])
822 829
823 830 def update_parser(self):
824 831 """
825 832 Abstract method. Allows for the class' parser to be updated
826 833 before the superclass' `run` method is called. Necessary to
827 834 allow options/arguments to be passed through to the underlying
828 835 celery command.
829 836 """
830 837 raise NotImplementedError("Abstract Method.")
831 838
832 839 def bootstrap_config(self, conf):
833 840 """
834 841 Loads the pylons configuration.
835 842 """
836 843 from pylons import config as pylonsconfig
837 844
838 845 self.path_to_ini_file = os.path.realpath(conf)
839 846 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
840 847 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
841 848
842 849 def _init_session(self):
843 850 """
844 851 Inits SqlAlchemy Session
845 852 """
846 853 logging.config.fileConfig(self.path_to_ini_file)
847 854 from pylons import config
848 855 from rhodecode.config.utils import initialize_database
849 856
850 857 # get to remove repos !!
851 858 add_cache(config)
852 859 initialize_database(config)
853 860
854 861
855 862 class PartialRenderer(object):
856 863 """
857 864 Partial renderer used to render chunks of html used in datagrids
858 865 use like::
859 866
860 867 _render = PartialRenderer('data_table/_dt_elements.mako')
861 868 _render('quick_menu', args, kwargs)
862 869 PartialRenderer.h,
863 870 c,
864 871 _,
865 872 ungettext
866 873 are the template stuff initialized inside and can be re-used later
867 874
868 875 :param tmpl_name: template path relate to /templates/ dir
869 876 """
870 877
871 878 def __init__(self, tmpl_name):
872 879 import rhodecode
873 880 from pylons import request, tmpl_context as c
874 881 from pylons.i18n.translation import _, ungettext
875 882 from rhodecode.lib import helpers as h
876 883
877 884 self.tmpl_name = tmpl_name
878 885 self.rhodecode = rhodecode
879 886 self.c = c
880 887 self._ = _
881 888 self.ungettext = ungettext
882 889 self.h = h
883 890 self.request = request
884 891
885 892 def _mako_lookup(self):
886 893 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
887 894 return _tmpl_lookup.get_template(self.tmpl_name)
888 895
889 896 def _update_kwargs_for_render(self, kwargs):
890 897 """
891 898 Inject params required for Mako rendering
892 899 """
893 900 _kwargs = {
894 901 '_': self._,
895 902 'h': self.h,
896 903 'c': self.c,
897 904 'request': self.request,
898 905 '_ungettext': self.ungettext,
899 906 }
900 907 _kwargs.update(kwargs)
901 908 return _kwargs
902 909
903 910 def _render_with_exc(self, render_func, args, kwargs):
904 911 try:
905 912 return render_func.render(*args, **kwargs)
906 913 except:
907 914 log.error(exceptions.text_error_template().render())
908 915 raise
909 916
910 917 def _get_template(self, template_obj, def_name):
911 918 if def_name:
912 919 tmpl = template_obj.get_def(def_name)
913 920 else:
914 921 tmpl = template_obj
915 922 return tmpl
916 923
917 924 def render(self, def_name, *args, **kwargs):
918 925 lookup_obj = self._mako_lookup()
919 926 tmpl = self._get_template(lookup_obj, def_name=def_name)
920 927 kwargs = self._update_kwargs_for_render(kwargs)
921 928 return self._render_with_exc(tmpl, args, kwargs)
922 929
923 930 def __call__(self, tmpl, *args, **kwargs):
924 931 return self.render(tmpl, *args, **kwargs)
925 932
926 933
927 934 def password_changed(auth_user, session):
928 935 # Never report password change in case of default user or anonymous user.
929 936 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
930 937 return False
931 938
932 939 password_hash = md5(auth_user.password) if auth_user.password else None
933 940 rhodecode_user = session.get('rhodecode_user', {})
934 941 session_password_hash = rhodecode_user.get('password', '')
935 942 return password_hash != session_password_hash
936 943
937 944
938 945 def read_opensource_licenses():
939 946 global _license_cache
940 947
941 948 if not _license_cache:
942 949 licenses = pkg_resources.resource_string(
943 950 'rhodecode', 'config/licenses.json')
944 951 _license_cache = json.loads(licenses)
945 952
946 953 return _license_cache
947 954
948 955
949 956 def get_registry(request):
950 957 """
951 958 Utility to get the pyramid registry from a request. During migration to
952 959 pyramid we sometimes want to use the pyramid registry from pylons context.
953 960 Therefore this utility returns `request.registry` for pyramid requests and
954 961 uses `get_current_registry()` for pylons requests.
955 962 """
956 963 try:
957 964 return request.registry
958 965 except AttributeError:
959 966 return get_current_registry()
960 967
961 968
962 969 def generate_platform_uuid():
963 970 """
964 971 Generates platform UUID based on it's name
965 972 """
966 973 import platform
967 974
968 975 try:
969 976 uuid_list = [platform.platform()]
970 977 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
971 978 except Exception as e:
972 979 log.error('Failed to generate host uuid: %s' % e)
973 980 return 'UNDEFINED'
@@ -1,102 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 repository permission model for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 from rhodecode.model import BaseModel
28 28 from rhodecode.model.db import UserRepoToPerm, UserGroupRepoToPerm, \
29 29 Permission
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 class RepositoryPermissionModel(BaseModel):
35 35
36 36 cls = UserRepoToPerm
37 37
38 38 def get_user_permission(self, repository, user):
39 39 repository = self._get_repo(repository)
40 40 user = self._get_user(user)
41 41
42 42 return UserRepoToPerm.query() \
43 43 .filter(UserRepoToPerm.user == user) \
44 44 .filter(UserRepoToPerm.repository == repository) \
45 45 .scalar()
46 46
47 47 def update_user_permission(self, repository, user, permission):
48 48 permission = Permission.get_by_key(permission)
49 49 current = self.get_user_permission(repository, user)
50 50 if current:
51 51 if not current.permission is permission:
52 52 current.permission = permission
53 53 else:
54 54 p = UserRepoToPerm()
55 55 p.user = user
56 56 p.repository = repository
57 57 p.permission = permission
58 58 self.sa.add(p)
59 59
60 60 def delete_user_permission(self, repository, user):
61 61 current = self.get_user_permission(repository, user)
62 62 if current:
63 63 self.sa.delete(current)
64 64
65 65 def get_users_group_permission(self, repository, users_group):
66 66 return UserGroupRepoToPerm.query() \
67 67 .filter(UserGroupRepoToPerm.users_group == users_group) \
68 68 .filter(UserGroupRepoToPerm.repository == repository) \
69 69 .scalar()
70 70
71 def update_users_group_permission(self, repository, users_group,
72 permission):
71 def update_user_group_permission(self, repository, users_group,
72 permission):
73 73 permission = Permission.get_by_key(permission)
74 74 current = self.get_users_group_permission(repository, users_group)
75 75 if current:
76 76 if not current.permission is permission:
77 77 current.permission = permission
78 78 else:
79 79 p = UserGroupRepoToPerm()
80 80 p.users_group = users_group
81 81 p.repository = repository
82 82 p.permission = permission
83 83 self.sa.add(p)
84 84
85 85 def delete_users_group_permission(self, repository, users_group):
86 86 current = self.get_users_group_permission(repository, users_group)
87 87 if current:
88 88 self.sa.delete(current)
89 89
90 90 def update_or_delete_user_permission(self, repository, user, permission):
91 91 if permission:
92 92 self.update_user_permission(repository, user, permission)
93 93 else:
94 94 self.delete_user_permission(repository, user)
95 95
96 96 def update_or_delete_users_group_permission(self, repository, user_group,
97 97 permission):
98 98 if permission:
99 self.update_users_group_permission(repository, user_group,
99 self.update_user_group_permission(repository, user_group,
100 100 permission)
101 101 else:
102 102 self.delete_users_group_permission(repository, user_group)
@@ -1,623 +1,637 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21
22 """
23 user group model for RhodeCode
24 """
25
26
27 21 import logging
28 22 import traceback
29 23
30 24 from rhodecode.lib.utils2 import safe_str, safe_unicode
31 25 from rhodecode.lib.exceptions import (
32 26 UserGroupAssignedException, RepoGroupAssignmentError)
33 27 from rhodecode.lib.utils2 import (
34 28 get_current_rhodecode_user, action_logger_generic)
35 29 from rhodecode.model import BaseModel
36 30 from rhodecode.model.scm import UserGroupList
37 31 from rhodecode.model.db import (
38 32 true, func, User, UserGroupMember, UserGroup,
39 33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
40 34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
41 35
42 36
43 37 log = logging.getLogger(__name__)
44 38
45 39
46 40 class UserGroupModel(BaseModel):
47 41
48 42 cls = UserGroup
49 43
50 44 def _get_user_group(self, user_group):
51 45 return self._get_instance(UserGroup, user_group,
52 46 callback=UserGroup.get_by_group_name)
53 47
54 48 def _create_default_perms(self, user_group):
55 49 # create default permission
56 50 default_perm = 'usergroup.read'
57 51 def_user = User.get_default_user()
58 52 for p in def_user.user_perms:
59 53 if p.permission.permission_name.startswith('usergroup.'):
60 54 default_perm = p.permission.permission_name
61 55 break
62 56
63 57 user_group_to_perm = UserUserGroupToPerm()
64 58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
65 59
66 60 user_group_to_perm.user_group = user_group
67 61 user_group_to_perm.user_id = def_user.user_id
68 62 return user_group_to_perm
69 63
70 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
71 perm_deletions=None, check_perms=True, cur_user=None):
64 def update_permissions(
65 self, user_group, perm_additions=None, perm_updates=None,
66 perm_deletions=None, check_perms=True, cur_user=None):
67
72 68 from rhodecode.lib.auth import HasUserGroupPermissionAny
73 69 if not perm_additions:
74 70 perm_additions = []
75 71 if not perm_updates:
76 72 perm_updates = []
77 73 if not perm_deletions:
78 74 perm_deletions = []
79 75
80 76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
81 77
78 changes = {
79 'added': [],
80 'updated': [],
81 'deleted': []
82 }
82 83 # update permissions
83 84 for member_id, perm, member_type in perm_updates:
84 85 member_id = int(member_id)
85 86 if member_type == 'user':
87 member_name = User.get(member_id).username
86 88 # this updates existing one
87 89 self.grant_user_permission(
88 90 user_group=user_group, user=member_id, perm=perm
89 91 )
90 92 else:
91 93 # check if we have permissions to alter this usergroup
92 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 97 self.grant_user_group_permission(
95 target_user_group=user_group, user_group=member_id, perm=perm
96 )
98 target_user_group=user_group, user_group=member_id, perm=perm)
99
100 changes['updated'].append({'type': member_type, 'id': member_id,
101 'name': member_name, 'new_perm': perm})
97 102
98 103 # set new permissions
99 104 for member_id, perm, member_type in perm_additions:
100 105 member_id = int(member_id)
101 106 if member_type == 'user':
107 member_name = User.get(member_id).username
102 108 self.grant_user_permission(
103 user_group=user_group, user=member_id, perm=perm
104 )
109 user_group=user_group, user=member_id, perm=perm)
105 110 else:
106 111 # check if we have permissions to alter this usergroup
107 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 115 self.grant_user_group_permission(
110 target_user_group=user_group, user_group=member_id, perm=perm
111 )
116 target_user_group=user_group, user_group=member_id, perm=perm)
117
118 changes['added'].append({'type': member_type, 'id': member_id,
119 'name': member_name, 'new_perm': perm})
112 120
113 121 # delete permissions
114 122 for member_id, perm, member_type in perm_deletions:
115 123 member_id = int(member_id)
116 124 if member_type == 'user':
125 member_name = User.get(member_id).username
117 126 self.revoke_user_permission(user_group=user_group, user=member_id)
118 127 else:
119 128 # check if we have permissions to alter this usergroup
120 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 132 self.revoke_user_group_permission(
123 target_user_group=user_group, user_group=member_id
124 )
133 target_user_group=user_group, user_group=member_id)
134
135 changes['deleted'].append({'type': member_type, 'id': member_id,
136 'name': member_name, 'new_perm': perm})
137 return changes
125 138
126 139 def get(self, user_group_id, cache=False):
127 140 return UserGroup.get(user_group_id)
128 141
129 142 def get_group(self, user_group):
130 143 return self._get_user_group(user_group)
131 144
132 145 def get_by_name(self, name, cache=False, case_insensitive=False):
133 146 return UserGroup.get_by_group_name(name, cache, case_insensitive)
134 147
135 148 def create(self, name, description, owner, active=True, group_data=None):
136 149 try:
137 150 new_user_group = UserGroup()
138 151 new_user_group.user = self._get_user(owner)
139 152 new_user_group.users_group_name = name
140 153 new_user_group.user_group_description = description
141 154 new_user_group.users_group_active = active
142 155 if group_data:
143 156 new_user_group.group_data = group_data
144 157 self.sa.add(new_user_group)
145 158 perm_obj = self._create_default_perms(new_user_group)
146 159 self.sa.add(perm_obj)
147 160
148 161 self.grant_user_permission(user_group=new_user_group,
149 162 user=owner, perm='usergroup.admin')
150 163
151 164 return new_user_group
152 165 except Exception:
153 166 log.error(traceback.format_exc())
154 167 raise
155 168
156 169 def _get_memberships_for_user_ids(self, user_group, user_id_list):
157 170 members = []
158 171 for user_id in user_id_list:
159 172 member = self._get_membership(user_group.users_group_id, user_id)
160 173 members.append(member)
161 174 return members
162 175
163 176 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
164 177 current_members = user_group.members or []
165 178 current_members_ids = [m.user.user_id for m in current_members]
166 179
167 180 added_members = [
168 181 user_id for user_id in user_id_list
169 182 if user_id not in current_members_ids]
170 183 if user_id_list == []:
171 184 # all members were deleted
172 185 deleted_members = current_members_ids
173 186 else:
174 187 deleted_members = [
175 188 user_id for user_id in current_members_ids
176 189 if user_id not in user_id_list]
177 190
178 191 return added_members, deleted_members
179 192
180 193 def _set_users_as_members(self, user_group, user_ids):
181 194 user_group.members = []
182 195 self.sa.flush()
183 196 members = self._get_memberships_for_user_ids(
184 197 user_group, user_ids)
185 198 user_group.members = members
186 199 self.sa.add(user_group)
187 200
188 201 def _update_members_from_user_ids(self, user_group, user_ids):
189 202 added, removed = self._get_added_and_removed_user_ids(
190 203 user_group, user_ids)
191 204 self._set_users_as_members(user_group, user_ids)
192 205 self._log_user_changes('added to', user_group, added)
193 206 self._log_user_changes('removed from', user_group, removed)
194 207 return added, removed
195 208
196 209 def _clean_members_data(self, members_data):
197 210 if not members_data:
198 211 members_data = []
199 212
200 213 members = []
201 214 for user in members_data:
202 215 uid = int(user['member_user_id'])
203 216 if uid not in members and user['type'] in ['new', 'existing']:
204 217 members.append(uid)
205 218 return members
206 219
207 220 def update(self, user_group, form_data):
208 221 user_group = self._get_user_group(user_group)
209 222 if 'users_group_name' in form_data:
210 223 user_group.users_group_name = form_data['users_group_name']
211 224 if 'users_group_active' in form_data:
212 225 user_group.users_group_active = form_data['users_group_active']
213 226 if 'user_group_description' in form_data:
214 227 user_group.user_group_description = form_data[
215 228 'user_group_description']
216 229
217 230 # handle owner change
218 231 if 'user' in form_data:
219 232 owner = form_data['user']
220 233 if isinstance(owner, basestring):
221 234 owner = User.get_by_username(form_data['user'])
222 235
223 236 if not isinstance(owner, User):
224 237 raise ValueError(
225 238 'invalid owner for user group: %s' % form_data['user'])
226 239
227 240 user_group.user = owner
228 241
229 242 added_user_ids = []
230 243 removed_user_ids = []
231 244 if 'users_group_members' in form_data:
232 245 members_id_list = self._clean_members_data(
233 246 form_data['users_group_members'])
234 247 added_user_ids, removed_user_ids = \
235 248 self._update_members_from_user_ids(user_group, members_id_list)
236 249
237 250 self.sa.add(user_group)
238 251 return user_group, added_user_ids, removed_user_ids
239 252
240 253 def delete(self, user_group, force=False):
241 254 """
242 255 Deletes repository group, unless force flag is used
243 256 raises exception if there are members in that group, else deletes
244 257 group and users
245 258
246 259 :param user_group:
247 260 :param force:
248 261 """
249 262 user_group = self._get_user_group(user_group)
263 if not user_group:
264 return
265
250 266 try:
251 267 # check if this group is not assigned to repo
252 268 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
253 269 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
254 270 # check if this group is not assigned to repo
255 271 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
256 272 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
257 273
258 274 if (assigned_to_repo or assigned_to_repo_group) and not force:
259 275 assigned = ','.join(map(safe_str,
260 276 assigned_to_repo+assigned_to_repo_group))
261 277
262 278 raise UserGroupAssignedException(
263 279 'UserGroup assigned to %s' % (assigned,))
264 280 self.sa.delete(user_group)
265 281 except Exception:
266 282 log.error(traceback.format_exc())
267 283 raise
268 284
269 285 def _log_user_changes(self, action, user_group, user_or_users):
270 286 users = user_or_users
271 287 if not isinstance(users, (list, tuple)):
272 288 users = [users]
273 289 rhodecode_user = get_current_rhodecode_user()
274 290 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
275 291 group_name = user_group.users_group_name
276 292
277 293 for user_or_user_id in users:
278 294 user = self._get_user(user_or_user_id)
279 295 log_text = 'User {user} {action} {group}'.format(
280 296 action=action, user=user.username, group=group_name)
281 297 log.info('Logging action: {0} by {1} ip:{2}'.format(
282 298 log_text, rhodecode_user, ipaddr))
283 299
284 300 def _find_user_in_group(self, user, user_group):
285 301 user_group_member = None
286 302 for m in user_group.members:
287 303 if m.user_id == user.user_id:
288 304 # Found this user's membership row
289 305 user_group_member = m
290 306 break
291 307
292 308 return user_group_member
293 309
294 310 def _get_membership(self, user_group_id, user_id):
295 311 user_group_member = UserGroupMember(user_group_id, user_id)
296 312 return user_group_member
297 313
298 314 def add_user_to_group(self, user_group, user):
299 315 user_group = self._get_user_group(user_group)
300 316 user = self._get_user(user)
301 317 user_member = self._find_user_in_group(user, user_group)
302 318 if user_member:
303 319 # user already in the group, skip
304 320 return True
305 321
306 322 member = self._get_membership(
307 323 user_group.users_group_id, user.user_id)
308 324 user_group.members.append(member)
309 325
310 326 try:
311 327 self.sa.add(member)
312 328 except Exception:
313 329 # what could go wrong here?
314 330 log.error(traceback.format_exc())
315 331 raise
316 332
317 333 self._log_user_changes('added to', user_group, user)
318 334 return member
319 335
320 336 def remove_user_from_group(self, user_group, user):
321 337 user_group = self._get_user_group(user_group)
322 338 user = self._get_user(user)
323 339 user_group_member = self._find_user_in_group(user, user_group)
324 340
325 341 if not user_group_member:
326 342 # User isn't in that group
327 343 return False
328 344
329 345 try:
330 346 self.sa.delete(user_group_member)
331 347 except Exception:
332 348 log.error(traceback.format_exc())
333 349 raise
334 350
335 351 self._log_user_changes('removed from', user_group, user)
336 352 return True
337 353
338 354 def has_perm(self, user_group, perm):
339 355 user_group = self._get_user_group(user_group)
340 356 perm = self._get_perm(perm)
341 357
342 358 return UserGroupToPerm.query()\
343 359 .filter(UserGroupToPerm.users_group == user_group)\
344 360 .filter(UserGroupToPerm.permission == perm).scalar() is not None
345 361
346 362 def grant_perm(self, user_group, perm):
347 363 user_group = self._get_user_group(user_group)
348 364 perm = self._get_perm(perm)
349 365
350 366 # if this permission is already granted skip it
351 367 _perm = UserGroupToPerm.query()\
352 368 .filter(UserGroupToPerm.users_group == user_group)\
353 369 .filter(UserGroupToPerm.permission == perm)\
354 370 .scalar()
355 371 if _perm:
356 372 return
357 373
358 374 new = UserGroupToPerm()
359 375 new.users_group = user_group
360 376 new.permission = perm
361 377 self.sa.add(new)
362 378 return new
363 379
364 380 def revoke_perm(self, user_group, perm):
365 381 user_group = self._get_user_group(user_group)
366 382 perm = self._get_perm(perm)
367 383
368 384 obj = UserGroupToPerm.query()\
369 385 .filter(UserGroupToPerm.users_group == user_group)\
370 386 .filter(UserGroupToPerm.permission == perm).scalar()
371 387 if obj:
372 388 self.sa.delete(obj)
373 389
374 390 def grant_user_permission(self, user_group, user, perm):
375 391 """
376 392 Grant permission for user on given user group, or update
377 393 existing one if found
378 394
379 395 :param user_group: Instance of UserGroup, users_group_id,
380 396 or users_group_name
381 397 :param user: Instance of User, user_id or username
382 398 :param perm: Instance of Permission, or permission_name
383 399 """
384 400
385 401 user_group = self._get_user_group(user_group)
386 402 user = self._get_user(user)
387 403 permission = self._get_perm(perm)
388 404
389 405 # check if we have that permission already
390 406 obj = self.sa.query(UserUserGroupToPerm)\
391 407 .filter(UserUserGroupToPerm.user == user)\
392 408 .filter(UserUserGroupToPerm.user_group == user_group)\
393 409 .scalar()
394 410 if obj is None:
395 411 # create new !
396 412 obj = UserUserGroupToPerm()
397 413 obj.user_group = user_group
398 414 obj.user = user
399 415 obj.permission = permission
400 416 self.sa.add(obj)
401 417 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
402 418 action_logger_generic(
403 419 'granted permission: {} to user: {} on usergroup: {}'.format(
404 420 perm, user, user_group), namespace='security.usergroup')
405 421
406 422 return obj
407 423
408 424 def revoke_user_permission(self, user_group, user):
409 425 """
410 426 Revoke permission for user on given user group
411 427
412 428 :param user_group: Instance of UserGroup, users_group_id,
413 429 or users_group name
414 430 :param user: Instance of User, user_id or username
415 431 """
416 432
417 433 user_group = self._get_user_group(user_group)
418 434 user = self._get_user(user)
419 435
420 436 obj = self.sa.query(UserUserGroupToPerm)\
421 437 .filter(UserUserGroupToPerm.user == user)\
422 438 .filter(UserUserGroupToPerm.user_group == user_group)\
423 439 .scalar()
424 440 if obj:
425 441 self.sa.delete(obj)
426 442 log.debug('Revoked perm on %s on %s', user_group, user)
427 443 action_logger_generic(
428 444 'revoked permission from user: {} on usergroup: {}'.format(
429 445 user, user_group), namespace='security.usergroup')
430 446
431 447 def grant_user_group_permission(self, target_user_group, user_group, perm):
432 448 """
433 449 Grant user group permission for given target_user_group
434 450
435 451 :param target_user_group:
436 452 :param user_group:
437 453 :param perm:
438 454 """
439 455 target_user_group = self._get_user_group(target_user_group)
440 456 user_group = self._get_user_group(user_group)
441 457 permission = self._get_perm(perm)
442 458 # forbid assigning same user group to itself
443 459 if target_user_group == user_group:
444 460 raise RepoGroupAssignmentError('target repo:%s cannot be '
445 461 'assigned to itself' % target_user_group)
446 462
447 463 # check if we have that permission already
448 464 obj = self.sa.query(UserGroupUserGroupToPerm)\
449 465 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
450 466 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
451 467 .scalar()
452 468 if obj is None:
453 469 # create new !
454 470 obj = UserGroupUserGroupToPerm()
455 471 obj.user_group = user_group
456 472 obj.target_user_group = target_user_group
457 473 obj.permission = permission
458 474 self.sa.add(obj)
459 475 log.debug(
460 476 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
461 477 action_logger_generic(
462 478 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
463 479 perm, user_group, target_user_group),
464 480 namespace='security.usergroup')
465 481
466 482 return obj
467 483
468 484 def revoke_user_group_permission(self, target_user_group, user_group):
469 485 """
470 486 Revoke user group permission for given target_user_group
471 487
472 488 :param target_user_group:
473 489 :param user_group:
474 490 """
475 491 target_user_group = self._get_user_group(target_user_group)
476 492 user_group = self._get_user_group(user_group)
477 493
478 494 obj = self.sa.query(UserGroupUserGroupToPerm)\
479 495 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
480 496 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
481 497 .scalar()
482 498 if obj:
483 499 self.sa.delete(obj)
484 500 log.debug(
485 501 'Revoked perm on %s on %s', target_user_group, user_group)
486 502 action_logger_generic(
487 503 'revoked permission from usergroup: {} on usergroup: {}'.format(
488 504 user_group, target_user_group),
489 505 namespace='security.repogroup')
490 506
491 507 def enforce_groups(self, user, groups, extern_type=None):
492 508 user = self._get_user(user)
493 509 log.debug('Enforcing groups %s on user %s', groups, user)
494 510 current_groups = user.group_member
495 511 # find the external created groups
496 512 externals = [x.users_group for x in current_groups
497 513 if 'extern_type' in x.users_group.group_data]
498 514
499 515 # calculate from what groups user should be removed
500 516 # externals that are not in groups
501 517 for gr in externals:
502 518 if gr.users_group_name not in groups:
503 519 log.debug('Removing user %s from user group %s', user, gr)
504 520 self.remove_user_from_group(gr, user)
505 521
506 522 # now we calculate in which groups user should be == groups params
507 523 owner = User.get_first_super_admin().username
508 524 for gr in set(groups):
509 525 existing_group = UserGroup.get_by_group_name(gr)
510 526 if not existing_group:
511 527 desc = 'Automatically created from plugin:%s' % extern_type
512 528 # we use first admin account to set the owner of the group
513 529 existing_group = UserGroupModel().create(
514 530 gr, desc, owner, group_data={'extern_type': extern_type})
515 531
516 532 # we can only add users to special groups created via plugins
517 533 managed = 'extern_type' in existing_group.group_data
518 534 if managed:
519 535 log.debug('Adding user %s to user group %s', user, gr)
520 536 UserGroupModel().add_user_to_group(existing_group, user)
521 537 else:
522 538 log.debug('Skipping addition to group %s since it is '
523 539 'not set to be automatically synchronized' % gr)
524 540
525 541 def change_groups(self, user, groups):
526 542 """
527 543 This method changes user group assignment
528 544 :param user: User
529 545 :param groups: array of UserGroupModel
530 :return:
531 546 """
532 547 user = self._get_user(user)
533 548 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
534 549 current_groups = user.group_member
535 550 current_groups = [x.users_group for x in current_groups]
536 551
537 552 # calculate from what groups user should be removed/add
538 553 groups = set(groups)
539 554 current_groups = set(current_groups)
540 555
541 556 groups_to_remove = current_groups - groups
542 557 groups_to_add = groups - current_groups
543 558
544 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 562 self.remove_user_from_group(gr.users_group_name, user.username)
547 563 for gr in groups_to_add:
548 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
549 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
564 log.debug('Adding user %s to user group %s',
565 user.username, gr.users_group_name)
566 UserGroupModel().add_user_to_group(
567 gr.users_group_name, user.username)
550 568
551 569 def _serialize_user_group(self, user_group):
552 570 import rhodecode.lib.helpers as h
553 571 return {
554 572 'id': user_group.users_group_id,
555 573 # TODO: marcink figure out a way to generate the url for the
556 574 # icon
557 575 'icon_link': '',
558 576 'value_display': 'Group: %s (%d members)' % (
559 577 user_group.users_group_name, len(user_group.members),),
560 578 'value': user_group.users_group_name,
561 579 'description': user_group.user_group_description,
562 580 'owner': user_group.user.username,
563 581
564 582 'owner_icon': h.gravatar_url(user_group.user.email, 30),
565 583 'value_display_owner': h.person(user_group.user.email),
566 584
567 585 'value_type': 'user_group',
568 586 'active': user_group.users_group_active,
569 587 }
570 588
571 589 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
572 590 expand_groups=False):
573 591 query = self.sa.query(UserGroup)
574 592 if only_active:
575 593 query = query.filter(UserGroup.users_group_active == true())
576 594
577 595 if name_contains:
578 596 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
579 597 query = query.filter(
580 598 UserGroup.users_group_name.ilike(ilike_expression))\
581 599 .order_by(func.length(UserGroup.users_group_name))\
582 600 .order_by(UserGroup.users_group_name)
583 601
584 602 query = query.limit(limit)
585 603 user_groups = query.all()
586 604 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
587 605 user_groups = UserGroupList(user_groups, perm_set=perm_set)
588 606
589 607 # store same serialize method to extract data from User
590 608 from rhodecode.model.user import UserModel
591 609 serialize_user = UserModel()._serialize_user
592 610
593 611 _groups = []
594 612 for group in user_groups:
595 613 entry = self._serialize_user_group(group)
596 614 if expand_groups:
597 615 expanded_members = []
598 616 for member in group.members:
599 617 expanded_members.append(serialize_user(member.user))
600 618 entry['members'] = expanded_members
601 619 _groups.append(entry)
602 620 return _groups
603 621
604 622 @staticmethod
605 623 def get_user_groups_as_dict(user_group):
606 624 import rhodecode.lib.helpers as h
607 625
608 626 data = {
609 627 'users_group_id': user_group.users_group_id,
610 628 'group_name': user_group.users_group_name,
611 629 'group_description': user_group.user_group_description,
612 630 'active': user_group.users_group_active,
613 631 "owner": user_group.user.username,
614 632 'owner_icon': h.gravatar_url(user_group.user.email, 30),
615 633 "owner_data": {
616 634 'owner': user_group.user.username,
617 635 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
618 636 }
619 637 return data
620
621
622
623
@@ -1,1122 +1,1122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 41 from rhodecode.authentication import (
42 42 legacy_plugin_prefix, _import_legacy_plugin)
43 43 from rhodecode.authentication.base import loadplugin
44 44 from rhodecode.config.routing import ADMIN_PREFIX
45 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
48 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 51 from rhodecode.model.db import (
52 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 # silence warnings and pylint
56 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class _Missing(object):
63 63 pass
64 64
65 65 Missing = _Missing()
66 66
67 67
68 68 class StateObj(object):
69 69 """
70 70 this is needed to translate the messages using _() in validators
71 71 """
72 72 _ = staticmethod(_)
73 73
74 74
75 75 def M(self, key, state=None, **kwargs):
76 76 """
77 77 returns string from self.message based on given key,
78 78 passed kw params are used to substitute %(named)s params inside
79 79 translated strings
80 80
81 81 :param msg:
82 82 :param state:
83 83 """
84 84 if state is None:
85 85 state = StateObj()
86 86 else:
87 87 state._ = staticmethod(_)
88 88 # inject validator into state object
89 89 return self.message(key, state, **kwargs)
90 90
91 91
92 92 def UniqueList(convert=None):
93 93 class _UniqueList(formencode.FancyValidator):
94 94 """
95 95 Unique List !
96 96 """
97 97 messages = {
98 98 'empty': _(u'Value cannot be an empty list'),
99 99 'missing_value': _(u'Value cannot be an empty list'),
100 100 }
101 101
102 102 def _to_python(self, value, state):
103 103 ret_val = []
104 104
105 105 def make_unique(value):
106 106 seen = []
107 107 return [c for c in value if not (c in seen or seen.append(c))]
108 108
109 109 if isinstance(value, list):
110 110 ret_val = make_unique(value)
111 111 elif isinstance(value, set):
112 112 ret_val = make_unique(list(value))
113 113 elif isinstance(value, tuple):
114 114 ret_val = make_unique(list(value))
115 115 elif value is None:
116 116 ret_val = []
117 117 else:
118 118 ret_val = [value]
119 119
120 120 if convert:
121 121 ret_val = map(convert, ret_val)
122 122 return ret_val
123 123
124 124 def empty_value(self, value):
125 125 return []
126 126
127 127 return _UniqueList
128 128
129 129
130 130 def UniqueListFromString():
131 131 class _UniqueListFromString(UniqueList()):
132 132 def _to_python(self, value, state):
133 133 if isinstance(value, basestring):
134 134 value = aslist(value, ',')
135 135 return super(_UniqueListFromString, self)._to_python(value, state)
136 136 return _UniqueListFromString
137 137
138 138
139 139 def ValidSvnPattern(section, repo_name=None):
140 140 class _validator(formencode.validators.FancyValidator):
141 141 messages = {
142 142 'pattern_exists': _(u'Pattern already exists'),
143 143 }
144 144
145 145 def validate_python(self, value, state):
146 146 if not value:
147 147 return
148 148 model = VcsSettingsModel(repo=repo_name)
149 149 ui_settings = model.get_svn_patterns(section=section)
150 150 for entry in ui_settings:
151 151 if value == entry.value:
152 152 msg = M(self, 'pattern_exists', state)
153 153 raise formencode.Invalid(msg, value, state)
154 154 return _validator
155 155
156 156
157 157 def ValidUsername(edit=False, old_data={}):
158 158 class _validator(formencode.validators.FancyValidator):
159 159 messages = {
160 160 'username_exists': _(u'Username "%(username)s" already exists'),
161 161 'system_invalid_username':
162 162 _(u'Username "%(username)s" is forbidden'),
163 163 'invalid_username':
164 164 _(u'Username may only contain alphanumeric characters '
165 165 u'underscores, periods or dashes and must begin with '
166 166 u'alphanumeric character or underscore')
167 167 }
168 168
169 169 def validate_python(self, value, state):
170 170 if value in ['default', 'new_user']:
171 171 msg = M(self, 'system_invalid_username', state, username=value)
172 172 raise formencode.Invalid(msg, value, state)
173 173 # check if user is unique
174 174 old_un = None
175 175 if edit:
176 176 old_un = User.get(old_data.get('user_id')).username
177 177
178 178 if old_un != value or not edit:
179 179 if User.get_by_username(value, case_insensitive=True):
180 180 msg = M(self, 'username_exists', state, username=value)
181 181 raise formencode.Invalid(msg, value, state)
182 182
183 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 184 is None):
185 185 msg = M(self, 'invalid_username', state)
186 186 raise formencode.Invalid(msg, value, state)
187 187 return _validator
188 188
189 189
190 190 def ValidRegex(msg=None):
191 191 class _validator(formencode.validators.Regex):
192 192 messages = {'invalid': msg or _(u'The input is not valid')}
193 193 return _validator
194 194
195 195
196 196 def ValidRepoUser(allow_disabled=False):
197 197 class _validator(formencode.validators.FancyValidator):
198 198 messages = {
199 199 'invalid_username': _(u'Username %(username)s is not valid'),
200 200 'disabled_username': _(u'Username %(username)s is disabled')
201 201 }
202 202
203 203 def validate_python(self, value, state):
204 204 try:
205 205 user = User.query().filter(User.username == value).one()
206 206 except Exception:
207 207 msg = M(self, 'invalid_username', state, username=value)
208 208 raise formencode.Invalid(
209 209 msg, value, state, error_dict={'username': msg}
210 210 )
211 211 if user and (not allow_disabled and not user.active):
212 212 msg = M(self, 'disabled_username', state, username=value)
213 213 raise formencode.Invalid(
214 214 msg, value, state, error_dict={'username': msg}
215 215 )
216 216
217 217 return _validator
218 218
219 219
220 220 def ValidUserGroup(edit=False, old_data={}):
221 221 class _validator(formencode.validators.FancyValidator):
222 222 messages = {
223 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 225 'invalid_usergroup_name':
226 226 _(u'user group name may only contain alphanumeric '
227 227 u'characters underscores, periods or dashes and must begin '
228 228 u'with alphanumeric character')
229 229 }
230 230
231 231 def validate_python(self, value, state):
232 232 if value in ['default']:
233 233 msg = M(self, 'invalid_group', state)
234 234 raise formencode.Invalid(
235 235 msg, value, state, error_dict={'users_group_name': msg}
236 236 )
237 237 # check if group is unique
238 238 old_ugname = None
239 239 if edit:
240 240 old_id = old_data.get('users_group_id')
241 241 old_ugname = UserGroup.get(old_id).users_group_name
242 242
243 243 if old_ugname != value or not edit:
244 244 is_existing_group = UserGroup.get_by_group_name(
245 245 value, case_insensitive=True)
246 246 if is_existing_group:
247 247 msg = M(self, 'group_exist', state, usergroup=value)
248 248 raise formencode.Invalid(
249 249 msg, value, state, error_dict={'users_group_name': msg}
250 250 )
251 251
252 252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
253 253 msg = M(self, 'invalid_usergroup_name', state)
254 254 raise formencode.Invalid(
255 255 msg, value, state, error_dict={'users_group_name': msg}
256 256 )
257 257
258 258 return _validator
259 259
260 260
261 261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
262 262 class _validator(formencode.validators.FancyValidator):
263 263 messages = {
264 264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 267 u'already exists'),
268 268 'permission_denied': _(u"no permission to store repository group"
269 269 u"in this location"),
270 270 'permission_denied_root': _(
271 271 u"no permission to store repository group "
272 272 u"in root location")
273 273 }
274 274
275 275 def _to_python(self, value, state):
276 276 group_name = repo_name_slug(value.get('group_name', ''))
277 277 group_parent_id = safe_int(value.get('group_parent_id'))
278 278 gr = RepoGroup.get(group_parent_id)
279 279 if gr:
280 280 parent_group_path = gr.full_path
281 281 # value needs to be aware of group name in order to check
282 282 # db key This is an actual just the name to store in the
283 283 # database
284 284 group_name_full = (
285 285 parent_group_path + RepoGroup.url_sep() + group_name)
286 286 else:
287 287 group_name_full = group_name
288 288
289 289 value['group_name'] = group_name
290 290 value['group_name_full'] = group_name_full
291 291 value['group_parent_id'] = group_parent_id
292 292 return value
293 293
294 294 def validate_python(self, value, state):
295 295
296 296 old_group_name = None
297 297 group_name = value.get('group_name')
298 298 group_name_full = value.get('group_name_full')
299 299 group_parent_id = safe_int(value.get('group_parent_id'))
300 300 if group_parent_id == -1:
301 301 group_parent_id = None
302 302
303 303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 304 parent_group_changed = False
305 305 if edit:
306 306 old_group_name = group_obj.group_name
307 307 old_group_parent_id = group_obj.group_parent_id
308 308
309 309 if group_parent_id != old_group_parent_id:
310 310 parent_group_changed = True
311 311
312 312 # TODO: mikhail: the following if statement is not reached
313 313 # since group_parent_id's OneOf validation fails before.
314 314 # Can be removed.
315 315
316 316 # check against setting a parent of self
317 317 parent_of_self = (
318 318 old_data['group_id'] == group_parent_id
319 319 if group_parent_id else False
320 320 )
321 321 if parent_of_self:
322 322 msg = M(self, 'group_parent_id', state)
323 323 raise formencode.Invalid(
324 324 msg, value, state, error_dict={'group_parent_id': msg}
325 325 )
326 326
327 327 # group we're moving current group inside
328 328 child_group = None
329 329 if group_parent_id:
330 330 child_group = RepoGroup.query().filter(
331 331 RepoGroup.group_id == group_parent_id).scalar()
332 332
333 333 # do a special check that we cannot move a group to one of
334 334 # it's children
335 335 if edit and child_group:
336 336 parents = [x.group_id for x in child_group.parents]
337 337 move_to_children = old_data['group_id'] in parents
338 338 if move_to_children:
339 339 msg = M(self, 'group_parent_id', state)
340 340 raise formencode.Invalid(
341 341 msg, value, state, error_dict={'group_parent_id': msg})
342 342
343 343 # Check if we have permission to store in the parent.
344 344 # Only check if the parent group changed.
345 345 if parent_group_changed:
346 346 if child_group is None:
347 347 if not can_create_in_root:
348 348 msg = M(self, 'permission_denied_root', state)
349 349 raise formencode.Invalid(
350 350 msg, value, state,
351 351 error_dict={'group_parent_id': msg})
352 352 else:
353 353 valid = HasRepoGroupPermissionAny('group.admin')
354 354 forbidden = not valid(
355 355 child_group.group_name, 'can create group validator')
356 356 if forbidden:
357 357 msg = M(self, 'permission_denied', state)
358 358 raise formencode.Invalid(
359 359 msg, value, state,
360 360 error_dict={'group_parent_id': msg})
361 361
362 362 # if we change the name or it's new group, check for existing names
363 363 # or repositories with the same name
364 364 if old_group_name != group_name_full or not edit:
365 365 # check group
366 366 gr = RepoGroup.get_by_group_name(group_name_full)
367 367 if gr:
368 368 msg = M(self, 'group_exists', state, group_name=group_name)
369 369 raise formencode.Invalid(
370 370 msg, value, state, error_dict={'group_name': msg})
371 371
372 372 # check for same repo
373 373 repo = Repository.get_by_repo_name(group_name_full)
374 374 if repo:
375 375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 376 raise formencode.Invalid(
377 377 msg, value, state, error_dict={'group_name': msg})
378 378
379 379 return _validator
380 380
381 381
382 382 def ValidPassword():
383 383 class _validator(formencode.validators.FancyValidator):
384 384 messages = {
385 385 'invalid_password':
386 386 _(u'Invalid characters (non-ascii) in password')
387 387 }
388 388
389 389 def validate_python(self, value, state):
390 390 try:
391 391 (value or '').decode('ascii')
392 392 except UnicodeError:
393 393 msg = M(self, 'invalid_password', state)
394 394 raise formencode.Invalid(msg, value, state,)
395 395 return _validator
396 396
397 397
398 398 def ValidOldPassword(username):
399 399 class _validator(formencode.validators.FancyValidator):
400 400 messages = {
401 401 'invalid_password': _(u'Invalid old password')
402 402 }
403 403
404 404 def validate_python(self, value, state):
405 405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
406 406 if not authenticate(username, value, '', HTTP_TYPE):
407 407 msg = M(self, 'invalid_password', state)
408 408 raise formencode.Invalid(
409 409 msg, value, state, error_dict={'current_password': msg}
410 410 )
411 411 return _validator
412 412
413 413
414 414 def ValidPasswordsMatch(
415 415 passwd='new_password', passwd_confirmation='password_confirmation'):
416 416 class _validator(formencode.validators.FancyValidator):
417 417 messages = {
418 418 'password_mismatch': _(u'Passwords do not match'),
419 419 }
420 420
421 421 def validate_python(self, value, state):
422 422
423 423 pass_val = value.get('password') or value.get(passwd)
424 424 if pass_val != value[passwd_confirmation]:
425 425 msg = M(self, 'password_mismatch', state)
426 426 raise formencode.Invalid(
427 427 msg, value, state,
428 428 error_dict={passwd: msg, passwd_confirmation: msg}
429 429 )
430 430 return _validator
431 431
432 432
433 433 def ValidAuth():
434 434 class _validator(formencode.validators.FancyValidator):
435 435 messages = {
436 436 'invalid_password': _(u'invalid password'),
437 437 'invalid_username': _(u'invalid user name'),
438 438 'disabled_account': _(u'Your account is disabled')
439 439 }
440 440
441 441 def validate_python(self, value, state):
442 442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
443 443
444 444 password = value['password']
445 445 username = value['username']
446 446
447 447 if not authenticate(username, password, '', HTTP_TYPE,
448 448 skip_missing=True):
449 449 user = User.get_by_username(username)
450 450 if user and not user.active:
451 451 log.warning('user %s is disabled', username)
452 452 msg = M(self, 'disabled_account', state)
453 453 raise formencode.Invalid(
454 454 msg, value, state, error_dict={'username': msg}
455 455 )
456 456 else:
457 457 log.warning('user `%s` failed to authenticate', username)
458 458 msg = M(self, 'invalid_username', state)
459 459 msg2 = M(self, 'invalid_password', state)
460 460 raise formencode.Invalid(
461 461 msg, value, state,
462 462 error_dict={'username': msg, 'password': msg2}
463 463 )
464 464 return _validator
465 465
466 466
467 467 def ValidAuthToken():
468 468 class _validator(formencode.validators.FancyValidator):
469 469 messages = {
470 470 'invalid_token': _(u'Token mismatch')
471 471 }
472 472
473 473 def validate_python(self, value, state):
474 474 if value != authentication_token():
475 475 msg = M(self, 'invalid_token', state)
476 476 raise formencode.Invalid(msg, value, state)
477 477 return _validator
478 478
479 479
480 480 def ValidRepoName(edit=False, old_data={}):
481 481 class _validator(formencode.validators.FancyValidator):
482 482 messages = {
483 483 'invalid_repo_name':
484 484 _(u'Repository name %(repo)s is disallowed'),
485 485 # top level
486 486 'repository_exists': _(u'Repository with name %(repo)s '
487 487 u'already exists'),
488 488 'group_exists': _(u'Repository group with name "%(repo)s" '
489 489 u'already exists'),
490 490 # inside a group
491 491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
492 492 u'exists in group "%(group)s"'),
493 493 'group_in_group_exists': _(
494 494 u'Repository group with name "%(repo)s" '
495 495 u'exists in group "%(group)s"'),
496 496 }
497 497
498 498 def _to_python(self, value, state):
499 499 repo_name = repo_name_slug(value.get('repo_name', ''))
500 500 repo_group = value.get('repo_group')
501 501 if repo_group:
502 502 gr = RepoGroup.get(repo_group)
503 503 group_path = gr.full_path
504 504 group_name = gr.group_name
505 505 # value needs to be aware of group name in order to check
506 506 # db key This is an actual just the name to store in the
507 507 # database
508 508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
509 509 else:
510 510 group_name = group_path = ''
511 511 repo_name_full = repo_name
512 512
513 513 value['repo_name'] = repo_name
514 514 value['repo_name_full'] = repo_name_full
515 515 value['group_path'] = group_path
516 516 value['group_name'] = group_name
517 517 return value
518 518
519 519 def validate_python(self, value, state):
520 520
521 521 repo_name = value.get('repo_name')
522 522 repo_name_full = value.get('repo_name_full')
523 523 group_path = value.get('group_path')
524 524 group_name = value.get('group_name')
525 525
526 526 if repo_name in [ADMIN_PREFIX, '']:
527 527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
528 528 raise formencode.Invalid(
529 529 msg, value, state, error_dict={'repo_name': msg})
530 530
531 531 rename = old_data.get('repo_name') != repo_name_full
532 532 create = not edit
533 533 if rename or create:
534 534
535 535 if group_path:
536 536 if Repository.get_by_repo_name(repo_name_full):
537 537 msg = M(self, 'repository_in_group_exists', state,
538 538 repo=repo_name, group=group_name)
539 539 raise formencode.Invalid(
540 540 msg, value, state, error_dict={'repo_name': msg})
541 541 if RepoGroup.get_by_group_name(repo_name_full):
542 542 msg = M(self, 'group_in_group_exists', state,
543 543 repo=repo_name, group=group_name)
544 544 raise formencode.Invalid(
545 545 msg, value, state, error_dict={'repo_name': msg})
546 546 else:
547 547 if RepoGroup.get_by_group_name(repo_name_full):
548 548 msg = M(self, 'group_exists', state, repo=repo_name)
549 549 raise formencode.Invalid(
550 550 msg, value, state, error_dict={'repo_name': msg})
551 551
552 552 if Repository.get_by_repo_name(repo_name_full):
553 553 msg = M(
554 554 self, 'repository_exists', state, repo=repo_name)
555 555 raise formencode.Invalid(
556 556 msg, value, state, error_dict={'repo_name': msg})
557 557 return value
558 558 return _validator
559 559
560 560
561 561 def ValidForkName(*args, **kwargs):
562 562 return ValidRepoName(*args, **kwargs)
563 563
564 564
565 565 def SlugifyName():
566 566 class _validator(formencode.validators.FancyValidator):
567 567
568 568 def _to_python(self, value, state):
569 569 return repo_name_slug(value)
570 570
571 571 def validate_python(self, value, state):
572 572 pass
573 573
574 574 return _validator
575 575
576 576
577 577 def CannotHaveGitSuffix():
578 578 class _validator(formencode.validators.FancyValidator):
579 579 messages = {
580 580 'has_git_suffix':
581 581 _(u'Repository name cannot end with .git'),
582 582 }
583 583
584 584 def _to_python(self, value, state):
585 585 return value
586 586
587 587 def validate_python(self, value, state):
588 588 if value and value.endswith('.git'):
589 589 msg = M(
590 590 self, 'has_git_suffix', state)
591 591 raise formencode.Invalid(
592 592 msg, value, state, error_dict={'repo_name': msg})
593 593
594 594 return _validator
595 595
596 596
597 597 def ValidCloneUri():
598 598 class InvalidCloneUrl(Exception):
599 599 allowed_prefixes = ()
600 600
601 601 def url_handler(repo_type, url):
602 602 config = make_db_config(clear_session=False)
603 603 if repo_type == 'hg':
604 604 allowed_prefixes = ('http', 'svn+http', 'git+http')
605 605
606 606 if 'http' in url[:4]:
607 607 # initially check if it's at least the proper URL
608 608 # or does it pass basic auth
609 609 MercurialRepository.check_url(url, config)
610 610 elif 'svn+http' in url[:8]: # svn->hg import
611 611 SubversionRepository.check_url(url, config)
612 612 elif 'git+http' in url[:8]: # git->hg import
613 613 raise NotImplementedError()
614 614 else:
615 615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
616 616 'Allowed url must start with one of %s'
617 617 % (url, ','.join(allowed_prefixes)))
618 618 exc.allowed_prefixes = allowed_prefixes
619 619 raise exc
620 620
621 621 elif repo_type == 'git':
622 622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
623 623 if 'http' in url[:4]:
624 624 # initially check if it's at least the proper URL
625 625 # or does it pass basic auth
626 626 GitRepository.check_url(url, config)
627 627 elif 'svn+http' in url[:8]: # svn->git import
628 628 raise NotImplementedError()
629 629 elif 'hg+http' in url[:8]: # hg->git import
630 630 raise NotImplementedError()
631 631 else:
632 632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
633 633 'Allowed url must start with one of %s'
634 634 % (url, ','.join(allowed_prefixes)))
635 635 exc.allowed_prefixes = allowed_prefixes
636 636 raise exc
637 637
638 638 class _validator(formencode.validators.FancyValidator):
639 639 messages = {
640 640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
641 641 'invalid_clone_uri': _(
642 642 u'Invalid clone url, provide a valid clone '
643 643 u'url starting with one of %(allowed_prefixes)s')
644 644 }
645 645
646 646 def validate_python(self, value, state):
647 647 repo_type = value.get('repo_type')
648 648 url = value.get('clone_uri')
649 649
650 650 if url:
651 651 try:
652 652 url_handler(repo_type, url)
653 653 except InvalidCloneUrl as e:
654 654 log.warning(e)
655 655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
656 656 allowed_prefixes=','.join(e.allowed_prefixes))
657 657 raise formencode.Invalid(msg, value, state,
658 658 error_dict={'clone_uri': msg})
659 659 except Exception:
660 660 log.exception('Url validation failed')
661 661 msg = M(self, 'clone_uri', rtype=repo_type)
662 662 raise formencode.Invalid(msg, value, state,
663 663 error_dict={'clone_uri': msg})
664 664 return _validator
665 665
666 666
667 667 def ValidForkType(old_data={}):
668 668 class _validator(formencode.validators.FancyValidator):
669 669 messages = {
670 670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
671 671 }
672 672
673 673 def validate_python(self, value, state):
674 674 if old_data['repo_type'] != value:
675 675 msg = M(self, 'invalid_fork_type', state)
676 676 raise formencode.Invalid(
677 677 msg, value, state, error_dict={'repo_type': msg}
678 678 )
679 679 return _validator
680 680
681 681
682 682 def CanWriteGroup(old_data=None):
683 683 class _validator(formencode.validators.FancyValidator):
684 684 messages = {
685 685 'permission_denied': _(
686 686 u"You do not have the permission "
687 687 u"to create repositories in this group."),
688 688 'permission_denied_root': _(
689 689 u"You do not have the permission to store repositories in "
690 690 u"the root location.")
691 691 }
692 692
693 693 def _to_python(self, value, state):
694 694 # root location
695 695 if value in [-1, "-1"]:
696 696 return None
697 697 return value
698 698
699 699 def validate_python(self, value, state):
700 700 gr = RepoGroup.get(value)
701 701 gr_name = gr.group_name if gr else None # None means ROOT location
702 702 # create repositories with write permission on group is set to true
703 703 create_on_write = HasPermissionAny(
704 704 'hg.create.write_on_repogroup.true')()
705 705 group_admin = HasRepoGroupPermissionAny('group.admin')(
706 706 gr_name, 'can write into group validator')
707 707 group_write = HasRepoGroupPermissionAny('group.write')(
708 708 gr_name, 'can write into group validator')
709 709 forbidden = not (group_admin or (group_write and create_on_write))
710 710 can_create_repos = HasPermissionAny(
711 711 'hg.admin', 'hg.create.repository')
712 712 gid = (old_data['repo_group'].get('group_id')
713 713 if (old_data and 'repo_group' in old_data) else None)
714 714 value_changed = gid != safe_int(value)
715 715 new = not old_data
716 716 # do check if we changed the value, there's a case that someone got
717 717 # revoked write permissions to a repository, he still created, we
718 718 # don't need to check permission if he didn't change the value of
719 719 # groups in form box
720 720 if value_changed or new:
721 721 # parent group need to be existing
722 722 if gr and forbidden:
723 723 msg = M(self, 'permission_denied', state)
724 724 raise formencode.Invalid(
725 725 msg, value, state, error_dict={'repo_type': msg}
726 726 )
727 727 # check if we can write to root location !
728 728 elif gr is None and not can_create_repos():
729 729 msg = M(self, 'permission_denied_root', state)
730 730 raise formencode.Invalid(
731 731 msg, value, state, error_dict={'repo_type': msg}
732 732 )
733 733
734 734 return _validator
735 735
736 736
737 737 def ValidPerms(type_='repo'):
738 738 if type_ == 'repo_group':
739 739 EMPTY_PERM = 'group.none'
740 740 elif type_ == 'repo':
741 741 EMPTY_PERM = 'repository.none'
742 742 elif type_ == 'user_group':
743 743 EMPTY_PERM = 'usergroup.none'
744 744
745 745 class _validator(formencode.validators.FancyValidator):
746 746 messages = {
747 747 'perm_new_member_name':
748 748 _(u'This username or user group name is not valid')
749 749 }
750 750
751 751 def _to_python(self, value, state):
752 752 perm_updates = OrderedSet()
753 753 perm_additions = OrderedSet()
754 754 perm_deletions = OrderedSet()
755 755 # build a list of permission to update/delete and new permission
756 756
757 757 # Read the perm_new_member/perm_del_member attributes and group
758 758 # them by they IDs
759 759 new_perms_group = defaultdict(dict)
760 760 del_perms_group = defaultdict(dict)
761 761 for k, v in value.copy().iteritems():
762 762 if k.startswith('perm_del_member'):
763 763 # delete from org storage so we don't process that later
764 764 del value[k]
765 765 # part is `id`, `type`
766 766 _type, part = k.split('perm_del_member_')
767 767 args = part.split('_')
768 768 if len(args) == 2:
769 769 _key, pos = args
770 770 del_perms_group[pos][_key] = v
771 771 if k.startswith('perm_new_member'):
772 772 # delete from org storage so we don't process that later
773 773 del value[k]
774 774 # part is `id`, `type`, `perm`
775 775 _type, part = k.split('perm_new_member_')
776 776 args = part.split('_')
777 777 if len(args) == 2:
778 778 _key, pos = args
779 779 new_perms_group[pos][_key] = v
780 780
781 781 # store the deletes
782 782 for k in sorted(del_perms_group.keys()):
783 783 perm_dict = del_perms_group[k]
784 784 del_member = perm_dict.get('id')
785 785 del_type = perm_dict.get('type')
786 786 if del_member and del_type:
787 787 perm_deletions.add(
788 788 (del_member, None, del_type))
789 789
790 790 # store additions in order of how they were added in web form
791 791 for k in sorted(new_perms_group.keys()):
792 792 perm_dict = new_perms_group[k]
793 793 new_member = perm_dict.get('id')
794 794 new_type = perm_dict.get('type')
795 795 new_perm = perm_dict.get('perm')
796 796 if new_member and new_perm and new_type:
797 797 perm_additions.add(
798 798 (new_member, new_perm, new_type))
799 799
800 800 # get updates of permissions
801 801 # (read the existing radio button states)
802 802 default_user_id = User.get_default_user().user_id
803 803 for k, update_value in value.iteritems():
804 804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
805 805 member = k[7:]
806 806 update_type = {'u': 'user',
807 807 'g': 'users_group'}[k[0]]
808 808
809 809 if safe_int(member) == default_user_id:
810 810 if str2bool(value.get('repo_private')):
811 811 # prevent from updating default user permissions
812 812 # when this repository is marked as private
813 813 update_value = EMPTY_PERM
814 814
815 815 perm_updates.add(
816 816 (member, update_value, update_type))
817 817
818 818 value['perm_additions'] = [] # propagated later
819 819 value['perm_updates'] = list(perm_updates)
820 820 value['perm_deletions'] = list(perm_deletions)
821 821
822 822 updates_map = dict(
823 823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 824 # make sure Additions don't override updates.
825 825 for member_id, perm, member_type in list(perm_additions):
826 826 if member_id in updates_map:
827 827 perm = updates_map[member_id][0]
828 828 value['perm_additions'].append((member_id, perm, member_type))
829 829
830 830 # on new entries validate users they exist and they are active !
831 831 # this leaves feedback to the form
832 832 try:
833 833 if member_type == 'user':
834 834 User.query()\
835 835 .filter(User.active == true())\
836 836 .filter(User.user_id == member_id).one()
837 837 if member_type == 'users_group':
838 838 UserGroup.query()\
839 839 .filter(UserGroup.users_group_active == true())\
840 840 .filter(UserGroup.users_group_id == member_id)\
841 841 .one()
842 842
843 843 except Exception:
844 844 log.exception('Updated permission failed: org_exc:')
845 845 msg = M(self, 'perm_new_member_type', state)
846 846 raise formencode.Invalid(
847 847 msg, value, state, error_dict={
848 848 'perm_new_member_name': msg}
849 849 )
850 850 return value
851 851 return _validator
852 852
853 853
854 854 def ValidSettings():
855 855 class _validator(formencode.validators.FancyValidator):
856 856 def _to_python(self, value, state):
857 857 # settings form for users that are not admin
858 858 # can't edit certain parameters, it's extra backup if they mangle
859 859 # with forms
860 860
861 861 forbidden_params = [
862 862 'user', 'repo_type', 'repo_enable_locking',
863 863 'repo_enable_downloads', 'repo_enable_statistics'
864 864 ]
865 865
866 866 for param in forbidden_params:
867 867 if param in value:
868 868 del value[param]
869 869 return value
870 870
871 871 def validate_python(self, value, state):
872 872 pass
873 873 return _validator
874 874
875 875
876 876 def ValidPath():
877 877 class _validator(formencode.validators.FancyValidator):
878 878 messages = {
879 879 'invalid_path': _(u'This is not a valid path')
880 880 }
881 881
882 882 def validate_python(self, value, state):
883 883 if not os.path.isdir(value):
884 884 msg = M(self, 'invalid_path', state)
885 885 raise formencode.Invalid(
886 886 msg, value, state, error_dict={'paths_root_path': msg}
887 887 )
888 888 return _validator
889 889
890 890
891 891 def UniqSystemEmail(old_data={}):
892 892 class _validator(formencode.validators.FancyValidator):
893 893 messages = {
894 894 'email_taken': _(u'This e-mail address is already taken')
895 895 }
896 896
897 897 def _to_python(self, value, state):
898 898 return value.lower()
899 899
900 900 def validate_python(self, value, state):
901 901 if (old_data.get('email') or '').lower() != value:
902 902 user = User.get_by_email(value, case_insensitive=True)
903 903 if user:
904 904 msg = M(self, 'email_taken', state)
905 905 raise formencode.Invalid(
906 906 msg, value, state, error_dict={'email': msg}
907 907 )
908 908 return _validator
909 909
910 910
911 911 def ValidSystemEmail():
912 912 class _validator(formencode.validators.FancyValidator):
913 913 messages = {
914 914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
915 915 }
916 916
917 917 def _to_python(self, value, state):
918 918 return value.lower()
919 919
920 920 def validate_python(self, value, state):
921 921 user = User.get_by_email(value, case_insensitive=True)
922 922 if user is None:
923 923 msg = M(self, 'non_existing_email', state, email=value)
924 924 raise formencode.Invalid(
925 925 msg, value, state, error_dict={'email': msg}
926 926 )
927 927
928 928 return _validator
929 929
930 930
931 931 def NotReviewedRevisions(repo_id):
932 932 class _validator(formencode.validators.FancyValidator):
933 933 messages = {
934 934 'rev_already_reviewed':
935 935 _(u'Revisions %(revs)s are already part of pull request '
936 936 u'or have set status'),
937 937 }
938 938
939 939 def validate_python(self, value, state):
940 940 # check revisions if they are not reviewed, or a part of another
941 941 # pull request
942 942 statuses = ChangesetStatus.query()\
943 943 .filter(ChangesetStatus.revision.in_(value))\
944 944 .filter(ChangesetStatus.repo_id == repo_id)\
945 945 .all()
946 946
947 947 errors = []
948 948 for status in statuses:
949 949 if status.pull_request_id:
950 950 errors.append(['pull_req', status.revision[:12]])
951 951 elif status.status:
952 952 errors.append(['status', status.revision[:12]])
953 953
954 954 if errors:
955 955 revs = ','.join([x[1] for x in errors])
956 956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
957 957 raise formencode.Invalid(
958 958 msg, value, state, error_dict={'revisions': revs})
959 959
960 960 return _validator
961 961
962 962
963 963 def ValidIp():
964 964 class _validator(CIDR):
965 965 messages = {
966 966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
967 967 'illegalBits': _(
968 968 u'The network size (bits) must be within the range '
969 969 u'of 0-32 (not %(bits)r)'),
970 970 }
971 971
972 972 # we ovveride the default to_python() call
973 973 def to_python(self, value, state):
974 974 v = super(_validator, self).to_python(value, state)
975 975 v = safe_unicode(v.strip())
976 976 net = ipaddress.ip_network(address=v, strict=False)
977 977 return str(net)
978 978
979 979 def validate_python(self, value, state):
980 980 try:
981 981 addr = safe_unicode(value.strip())
982 982 # this raises an ValueError if address is not IpV4 or IpV6
983 983 ipaddress.ip_network(addr, strict=False)
984 984 except ValueError:
985 985 raise formencode.Invalid(self.message('badFormat', state),
986 986 value, state)
987 987
988 988 return _validator
989 989
990 990
991 991 def FieldKey():
992 992 class _validator(formencode.validators.FancyValidator):
993 993 messages = {
994 994 'badFormat': _(
995 995 u'Key name can only consist of letters, '
996 996 u'underscore, dash or numbers'),
997 997 }
998 998
999 999 def validate_python(self, value, state):
1000 1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1001 1001 raise formencode.Invalid(self.message('badFormat', state),
1002 1002 value, state)
1003 1003 return _validator
1004 1004
1005 1005
1006 1006 def ValidAuthPlugins():
1007 1007 class _validator(formencode.validators.FancyValidator):
1008 1008 messages = {
1009 1009 'import_duplicate': _(
1010 1010 u'Plugins %(loaded)s and %(next_to_load)s '
1011 1011 u'both export the same name'),
1012 1012 'missing_includeme': _(
1013 1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1014 1014 u'function.'),
1015 1015 'import_error': _(
1016 1016 u'Can not load plugin "%(plugin_id)s"'),
1017 1017 'no_plugin': _(
1018 1018 u'No plugin available with ID "%(plugin_id)s"'),
1019 1019 }
1020 1020
1021 1021 def _to_python(self, value, state):
1022 1022 # filter empty values
1023 1023 return filter(lambda s: s not in [None, ''], value)
1024 1024
1025 1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1026 1026 """
1027 1027 Validates that the plugin import works. It also checks that the
1028 1028 plugin has an includeme attribute.
1029 1029 """
1030 1030 try:
1031 1031 plugin = _import_legacy_plugin(plugin_id)
1032 1032 except Exception as e:
1033 1033 log.exception(
1034 1034 'Exception during import of auth legacy plugin "{}"'
1035 1035 .format(plugin_id))
1036 1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1037 1037 raise formencode.Invalid(msg, value, state)
1038 1038
1039 1039 if not hasattr(plugin, 'includeme'):
1040 1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1041 1041 raise formencode.Invalid(msg, value, state)
1042 1042
1043 1043 return plugin
1044 1044
1045 1045 def _validate_plugin_id(self, plugin_id, value, state):
1046 1046 """
1047 1047 Plugins are already imported during app start up. Therefore this
1048 1048 validation only retrieves the plugin from the plugin registry and
1049 1049 if it returns something not None everything is OK.
1050 1050 """
1051 1051 plugin = loadplugin(plugin_id)
1052 1052
1053 1053 if plugin is None:
1054 1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1055 1055 raise formencode.Invalid(msg, value, state)
1056 1056
1057 1057 return plugin
1058 1058
1059 1059 def validate_python(self, value, state):
1060 1060 unique_names = {}
1061 1061 for plugin_id in value:
1062 1062
1063 1063 # Validate legacy or normal plugin.
1064 1064 if plugin_id.startswith(legacy_plugin_prefix):
1065 1065 plugin = self._validate_legacy_plugin_id(
1066 1066 plugin_id, value, state)
1067 1067 else:
1068 1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1069 1069
1070 1070 # Only allow unique plugin names.
1071 1071 if plugin.name in unique_names:
1072 1072 msg = M(self, 'import_duplicate', state,
1073 1073 loaded=unique_names[plugin.name],
1074 1074 next_to_load=plugin)
1075 1075 raise formencode.Invalid(msg, value, state)
1076 1076 unique_names[plugin.name] = plugin
1077 1077
1078 1078 return _validator
1079 1079
1080 1080
1081 1081 def ValidPattern():
1082 1082
1083 1083 class _Validator(formencode.validators.FancyValidator):
1084 1084
1085 1085 def _to_python(self, value, state):
1086 1086 patterns = []
1087 1087
1088 1088 prefix = 'new_pattern'
1089 1089 for name, v in value.iteritems():
1090 1090 pattern_name = '_'.join((prefix, 'pattern'))
1091 1091 if name.startswith(pattern_name):
1092 1092 new_item_id = name[len(pattern_name)+1:]
1093 1093
1094 1094 def _field(name):
1095 1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1096 1096
1097 1097 values = {
1098 1098 'issuetracker_pat': value.get(_field('pattern')),
1099 1099 'issuetracker_pat': value.get(_field('pattern')),
1100 1100 'issuetracker_url': value.get(_field('url')),
1101 1101 'issuetracker_pref': value.get(_field('prefix')),
1102 1102 'issuetracker_desc': value.get(_field('description'))
1103 1103 }
1104 1104 new_uid = md5(values['issuetracker_pat'])
1105 1105
1106 1106 has_required_fields = (
1107 1107 values['issuetracker_pat']
1108 1108 and values['issuetracker_url'])
1109 1109
1110 1110 if has_required_fields:
1111 1111 settings = [
1112 1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1113 1113 for key in values]
1114 1114 patterns.append(settings)
1115 1115
1116 1116 value['patterns'] = patterns
1117 1117 delete_patterns = value.get('uid') or []
1118 1118 if not isinstance(delete_patterns, (list, tuple)):
1119 1119 delete_patterns = [delete_patterns]
1120 1120 value['delete_patterns'] = delete_patterns
1121 1121 return value
1122 1122 return _Validator
@@ -1,258 +1,269 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 16 pyroutes.register('favicon', '/favicon.ico', []);
17 17 pyroutes.register('robots', '/robots.txt', []);
18 18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
26 26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 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 29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
31 31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 37 pyroutes.register('admin_home', '/_admin', []);
38 38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
60 60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
61 61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
62 62 pyroutes.register('users', '/_admin/users', []);
63 63 pyroutes.register('users_data', '/_admin/users_data', []);
64 64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
65 65 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
66 66 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
67 67 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
68 68 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
69 69 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
70 70 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
71 71 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
72 72 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
73 73 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
74 74 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
75 75 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
76 76 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
77 77 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
78 78 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
79 79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
80 80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
81 81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
82 82 pyroutes.register('user_groups', '/_admin/user_groups', []);
83 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']);
85 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
86 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
84 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
85 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
87 86 pyroutes.register('repos', '/_admin/repos', []);
88 87 pyroutes.register('repo_new', '/_admin/repos/new', []);
89 88 pyroutes.register('repo_create', '/_admin/repos/create', []);
90 89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
91 90 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
92 91 pyroutes.register('channelstream_proxy', '/_channelstream', []);
93 92 pyroutes.register('login', '/_admin/login', []);
94 93 pyroutes.register('logout', '/_admin/logout', []);
95 94 pyroutes.register('register', '/_admin/register', []);
96 95 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 96 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 97 pyroutes.register('home', '/', []);
99 98 pyroutes.register('user_autocomplete_data', '/_users', []);
100 99 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
101 100 pyroutes.register('repo_list_data', '/_repos', []);
102 101 pyroutes.register('goto_switcher_data', '/_goto_data', []);
103 102 pyroutes.register('journal', '/_admin/journal', []);
104 103 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
105 104 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
106 105 pyroutes.register('journal_public', '/_admin/public_journal', []);
107 106 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
108 107 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
109 108 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
110 109 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
111 110 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
112 111 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
113 112 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
114 113 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
115 114 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
116 115 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
117 116 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
118 117 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
119 118 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
120 119 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
121 120 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
122 121 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
123 122 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
124 123 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
125 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 125 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
127 126 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
128 127 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
129 128 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
130 129 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 130 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
132 131 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
133 132 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 133 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 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 135 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 136 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
138 137 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 138 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 139 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 140 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 141 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 142 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 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 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 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 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 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 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 149 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
151 150 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
152 151 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
153 152 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
154 153 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 154 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
156 155 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
157 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 157 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
159 158 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
160 159 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
161 160 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
162 161 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
163 162 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
164 163 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
165 164 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
166 165 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
167 166 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
168 167 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
169 168 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
170 169 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
171 170 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
172 171 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
173 172 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
174 173 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
175 174 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
176 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 176 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
178 177 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
179 178 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
180 179 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
181 180 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
182 181 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
183 182 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
184 183 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
185 184 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
186 185 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
187 186 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
188 187 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
189 188 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
190 189 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
191 190 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
192 191 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
193 192 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
194 193 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
195 194 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
196 195 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
197 196 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
198 197 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
199 198 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
200 199 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
201 200 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
202 201 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
203 202 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
204 203 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
205 204 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
206 205 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
207 206 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
208 207 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
209 208 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
210 209 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
211 210 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
212 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 224 pyroutes.register('search', '/_admin/search', []);
214 225 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
215 226 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
216 227 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
217 228 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 229 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
219 230 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
220 231 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
221 232 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
222 233 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
223 234 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
224 235 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
225 236 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
226 237 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
227 238 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
228 239 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
229 240 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
230 241 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
231 242 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
232 243 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
233 244 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
234 245 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
235 246 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
236 247 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
237 248 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
238 249 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
239 250 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
240 251 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
241 252 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
242 253 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
243 254 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
244 255 pyroutes.register('gists_show', '/_admin/gists', []);
245 256 pyroutes.register('gists_new', '/_admin/gists/new', []);
246 257 pyroutes.register('gists_create', '/_admin/gists/create', []);
247 258 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
248 259 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
249 260 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
250 261 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
251 262 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
252 263 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
253 264 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
254 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 266 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
256 267 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
257 268 pyroutes.register('apiv2', '/_admin/api', []);
258 269 }
@@ -1,148 +1,148 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-user">${_('User/User Group')}</th>
16 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 19 %for _user in c.repo_group.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 28 <span class="user">
29 29 ${h.link_to_user(_user.username)}
30 30 %if getattr(_user, 'admin_row', None):
31 31 (${_('super admin')})
32 32 %endif
33 33 %if getattr(_user, 'owner_row', None):
34 34 (${_('owner')})
35 35 %endif
36 36 </span>
37 37 </td>
38 38 <td></td>
39 39 </tr>
40 40 %else:
41 41 ##forbid revoking permission from yourself, except if you're an super admin
42 42 <tr>
43 43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
44 44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none')}</td>
45 45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
46 46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
47 47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
48 48 <td class="td-user">
49 49 ${base.gravatar(_user.email, 16)}
50 50 <span class="user">
51 51 % if _user.username == h.DEFAULT_USER:
52 52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 53 % else:
54 54 ${h.link_to_user(_user.username)}
55 55 % endif
56 56 </span>
57 57 </td>
58 58 <td class="td-action">
59 59 %if _user.username != h.DEFAULT_USER:
60 60 <span class="btn btn-link btn-danger revoke_perm"
61 61 member="${_user.user_id}" member_type="user">
62 62 <i class="icon-remove"></i> ${_('Revoke')}
63 63 </span>
64 64 %endif
65 65 </td>
66 66 %else:
67 67 ## special case for current user permissions, we make sure he cannot take his own permissions
68 68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
69 69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
70 70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
71 71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
72 72 <td class="td-user">
73 73 ${base.gravatar(_user.email, 16)}
74 74 <span class="user">
75 75 % if _user.username == h.DEFAULT_USER:
76 76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 77 % else:
78 78 ${h.link_to_user(_user.username)}
79 79 % endif
80 80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 81 </span>
82 82 </td>
83 83 <td></td>
84 84 %endif
85 85 </tr>
86 86 %endif
87 87 %endfor
88 88
89 89 ## USER GROUPS
90 90 %for _user_group in c.repo_group.permission_user_groups():
91 91 <tr id="id${id(_user_group.users_group_name)}">
92 92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}</td>
93 93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
94 94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
95 95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</td>
96 96 <td class="td-componentname">
97 97 <i class="icon-group" ></i>
98 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 100 ${_user_group.users_group_name}
101 101 </a>
102 102 %else:
103 103 ${_user_group.users_group_name}
104 104 %endif
105 105 </td>
106 106 <td class="td-action">
107 107 <span class="btn btn-link btn-danger revoke_perm"
108 108 member="${_user_group.users_group_id}" member_type="user_group">
109 109 <i class="icon-remove"></i> ${_('Revoke')}
110 110 </span>
111 111 </td>
112 112 </tr>
113 113 %endfor
114 114
115 115 <tr class="new_members" id="add_perm_input"></tr>
116 116 </table>
117 117 <div id="add_perm" class="link">
118 118 ${_('Add new')}
119 119 </div>
120 120 <div class="fields">
121 121 <div class="field">
122 122 <div class="label label-radio">
123 123 ${_('Apply to children')}:
124 124 </div>
125 125 <div class="radios">
126 126 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
127 127 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
128 128 ${h.radio('recursive', 'repos', label=_('Repositories'))}
129 129 ${h.radio('recursive', 'all', label=_('Both'))}
130 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 131 </div>
132 132 </div>
133 133 </div>
134 134 <div class="buttons">
135 135 ${h.submit('save',_('Save'),class_="btn btn-primary")}
136 136 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
137 137 </div>
138 138 ${h.end_form()}
139 139 </div>
140 140 </div>
141 141 <script type="text/javascript">
142 142 $('#add_perm').on('click', function(e){
143 143 addNewPermInput($(this), 'group');
144 144 });
145 145 $('.revoke_perm').on('click', function(e){
146 146 markRevokePermInput($(this), 'group');
147 147 })
148 148 </script>
@@ -1,123 +1,123 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 19 %for _user in c.repo_info.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 28 ${h.link_to_user(_user.username)}
29 29 %if getattr(_user, 'admin_row', None):
30 30 (${_('super admin')})
31 31 %endif
32 32 %if getattr(_user, 'owner_row', None):
33 33 (${_('owner')})
34 34 %endif
35 35 </td>
36 36 <td></td>
37 37 </tr>
38 38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
39 39 <tr>
40 40 <td colspan="4">
41 41 <span class="private_repo_msg">
42 42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
43 43 </span>
44 44 </td>
45 45 <td class="private_repo_msg">
46 46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
47 47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
48 48 <td></td>
49 49 </tr>
50 50 %else:
51 51 <tr>
52 52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
56 56 <td class="td-user">
57 57 ${base.gravatar(_user.email, 16)}
58 58 <span class="user">
59 59 % if _user.username == h.DEFAULT_USER:
60 60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
61 61 % else:
62 62 ${h.link_to_user(_user.username)}
63 63 % endif
64 64 </span>
65 65 </td>
66 66 <td class="td-action">
67 67 %if _user.username != h.DEFAULT_USER:
68 68 <span class="btn btn-link btn-danger revoke_perm"
69 69 member="${_user.user_id}" member_type="user">
70 70 <i class="icon-remove"></i> ${_('Revoke')}
71 71 </span>
72 72 %endif
73 73 </td>
74 74 </tr>
75 75 %endif
76 76 %endfor
77 77
78 78 ## USER GROUPS
79 79 %for _user_group in c.repo_info.permission_user_groups():
80 80 <tr>
81 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 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 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 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 85 <td class="td-componentname">
86 86 <i class="icon-group" ></i>
87 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 89 ${_user_group.users_group_name}
90 90 </a>
91 91 %else:
92 92 ${_user_group.users_group_name}
93 93 %endif
94 94 </td>
95 95 <td class="td-action">
96 96 <span class="btn btn-link btn-danger revoke_perm"
97 97 member="${_user_group.users_group_id}" member_type="user_group">
98 98 <i class="icon-remove"></i> ${_('Revoke')}
99 99 </span>
100 100 </td>
101 101 </tr>
102 102 %endfor
103 103 <tr class="new_members" id="add_perm_input"></tr>
104 104 </table>
105 105 <div id="add_perm" class="link">
106 106 ${_('Add new')}
107 107 </div>
108 108 <div class="buttons">
109 109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
110 110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
111 111 </div>
112 112 ${h.end_form()}
113 113 </div>
114 114 </div>
115 115
116 116 <script type="text/javascript">
117 117 $('#add_perm').on('click', function(e){
118 118 addNewPermInput($(this), 'repository');
119 119 });
120 120 $('.revoke_perm').on('click', function(e){
121 121 markRevokePermInput($(this), 'repository');
122 122 });
123 123 </script>
@@ -1,72 +1,72 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add user group')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <%def name="breadcrumbs_links()">
11 11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 ${h.link_to(_('User groups'),h.url('users_groups'))}
13 ${h.link_to(_('User groups'),h.route_path('user_groups'))}
14 14 &raquo;
15 15 ${_('Add User Group')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='admin')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box main-content">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 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 30 <div class="form">
31 31 <!-- fields -->
32 32 <div class="fields">
33 33 <div class="field">
34 34 <div class="label">
35 35 <label for="users_group_name">${_('Group name')}:</label>
36 36 </div>
37 37 <div class="input">
38 38 ${h.text('users_group_name', class_='medium')}
39 39 </div>
40 40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label for="user_group_description">${_('Description')}:</label>
44 44 </div>
45 45 <div class="textarea editor">
46 46 ${h.textarea('user_group_description')}
47 47 <span class="help-block">${_('Short, optional description for this user group.')}</span>
48 48 </div>
49 49 </div>
50 50 <div class="field">
51 51 <div class="label">
52 52 <label for="users_group_active">${_('Active')}:</label>
53 53 </div>
54 54 <div class="checkboxes">
55 55 ${h.checkbox('users_group_active',value=True, checked='checked')}
56 56 </div>
57 57 </div>
58 58
59 59 <div class="buttons">
60 60 ${h.submit('save',_('Save'),class_="btn")}
61 61 </div>
62 62 </div>
63 63 </div>
64 64 ${h.end_form()}
65 65 </div>
66 66 </%def>
67 67
68 68 <script>
69 69 $(document).ready(function(){
70 70 $('#users_group_name').focus();
71 71 })
72 72 </script>
@@ -1,46 +1,46 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user group settings') % c.user_group.users_group_name}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
14 ${h.link_to(_('User Groups'),h.route_path('user_groups'))}
15 15 &raquo;
16 16 ${c.user_group.users_group_name}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28
29 29 ##main
30 30 <div class="sidebar-col-wrapper">
31 31 <div class="sidebar">
32 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>
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>
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>
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>
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.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.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.route_path('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
37 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 38 </ul>
39 39 </div>
40 40
41 41 <div class="main-content-full-width">
42 42 <%include file="/admin/user_groups/user_group_edit_${c.active}.mako"/>
43 43 </div>
44 44 </div>
45 45 </div>
46 46 </%def>
@@ -1,84 +1,84 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.user_group.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.user_group.created_on), '', '',),
7 7
8 8 (_('Members'), len(c.group_members_obj),'', [x for x in c.group_members_obj]),
9 9 (_('Automatic member sync'), 'Yes' if c.user_group.group_data.get('extern_type') else 'No', '', '',),
10 10
11 11 (_('Assigned to repositories'), len(c.group_to_repos),'', [x for x in c.group_to_repos]),
12 12 (_('Assigned to repo groups'), len(c.group_to_repo_groups), '', [x for x in c.group_to_repo_groups]),
13 13
14 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 18 <div class="panel panel-default">
19 19 <div class="panel-heading">
20 20 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
21 21 </div>
22 22 <div class="panel-body">
23 23 ${base.dt_info_panel(elems)}
24 24 </div>
25 25
26 26 </div>
27 27
28 28 <div class="panel panel-default">
29 29 <div class="panel-heading">
30 30 <h3 class="panel-title">${_('Group members sync')}</h3>
31 31 </div>
32 32 <div class="panel-body">
33 33 <% sync_type = c.user_group.group_data.get('extern_type') %>
34 34
35 35 % if sync_type:
36 36 <p>
37 37 ${_('This group is set to be automatically synchronised.')}<br/>
38 38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
39 39 </p>
40 40 % else:
41 41 <p>
42 42 ${_('This group is not set to be automatically synchronised')}
43 43 </p>
44 44 % endif
45 45
46 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 48 <div class="field">
49 49 <button class="btn btn-default" type="submit">
50 50 %if sync_type:
51 51 ${_('Disable synchronization')}
52 52 %else:
53 53 ${_('Enable synchronization')}
54 54 %endif
55 55 </button>
56 56 </div>
57 57 <div class="field">
58 58 <span class="help-block">
59 59 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
60 60 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
61 61 </span>
62 62 </div>
63 63 ${h.end_form()}
64 64 </div>
65 65
66 66 </div>
67 67 </div>
68 68
69 69
70 70 <div class="panel panel-danger">
71 71 <div class="panel-heading">
72 72 <h3 class="panel-title">${_('Delete User Group')}</h3>
73 73 </div>
74 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 76 ${h.hidden('force', 1)}
77 77 <button class="btn btn-small btn-danger" type="submit"
78 78 onclick="return confirm('${_('Confirm to delete user group `%(ugroup)s` with all permission assignments') % {'ugroup': c.user_group.users_group_name}}');">
79 79 <i class="icon-remove-sign"></i>
80 80 ${_('Delete This User Group')}
81 81 </button>
82 82 ${h.end_form()}
83 83 </div>
84 84 </div>
@@ -1,3 +1,3 b''
1 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 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
6 6 </div>
7 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 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th>${_('User/User Group')}</th>
16 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 19 %for _user in c.user_group.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 28 <span class="user">
29 29 ${h.link_to_user(_user.username)}
30 30 %if getattr(_user, 'admin_row', None):
31 31 (${_('super admin')})
32 32 %endif
33 33 %if getattr(_user, 'owner_row', None):
34 34 (${_('owner')})
35 35 %endif
36 36 </span>
37 37 </td>
38 38 <td></td>
39 39 </tr>
40 40 %else:
41 41 ##forbid revoking permission from yourself, except if you're an super admin
42 42 <tr>
43 43 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
44 44 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
45 45 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
46 46 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
47 47 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
48 48 <td class="td-user">
49 49 ${base.gravatar(_user.email, 16)}
50 50 <span class="user">
51 51 % if _user.username == h.DEFAULT_USER:
52 52 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
53 53 % else:
54 54 ${h.link_to_user(_user.username)}
55 55 % endif
56 56 </span>
57 57 </td>
58 58 <td class="td-action">
59 59 %if _user.username != h.DEFAULT_USER:
60 60 <span class="btn btn-link btn-danger revoke_perm"
61 61 member="${_user.user_id}" member_type="user">
62 62 <i class="icon-remove"></i> ${_('revoke')}
63 63 </span>
64 64 %endif
65 65 </td>
66 66 %else:
67 67 ## special case for current user permissions, we make sure he cannot take his own permissions
68 68 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
69 69 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
70 70 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
71 71 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
72 72 <td class="td-user">
73 73 ${base.gravatar(_user.email, 16)}
74 74 <span class="user">
75 75 % if _user.username == h.DEFAULT_USER:
76 76 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
77 77 % else:
78 78 ${h.link_to_user(_user.username)}
79 79 % endif
80 80 <span class="user-perm-help-text">(${_('delegated admin')})</span>
81 81 </span>
82 82 </td>
83 83 <td></td>
84 84 %endif
85 85 </tr>
86 86 %endif
87 87 %endfor
88 88
89 89 ## USER GROUPS
90 90 %for _user_group in c.user_group.permission_user_groups():
91 91 <tr>
92 92 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
93 93 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
94 94 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
95 95 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
96 96 <td class="td-user">
97 97 <i class="icon-group" ></i>
98 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 100 ${_user_group.users_group_name}
101 101 </a>
102 102 %else:
103 103 ${_user_group.users_group_name}
104 104 %endif
105 105 </td>
106 106 <td class="td-action">
107 107 <span class="btn btn-link btn-danger revoke_perm"
108 108 member="${_user_group.users_group_id}" member_type="user_group">
109 109 <i class="icon-remove"></i> ${_('revoke')}
110 110 </span>
111 111 </td>
112 112 </tr>
113 113 %endfor
114 114 <tr class="new_members" id="add_perm_input"></tr>
115 115 </table>
116 116 <div id="add_perm" class="link">
117 117 ${_('Add new')}
118 118 </div>
119 119 <div class="buttons">
120 120 ${h.submit('save',_('Save'),class_="btn btn-primary")}
121 121 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
122 122 </div>
123 123 ${h.end_form()}
124 124 </div>
125 125 </div>
126 126
127 127 <script type="text/javascript">
128 128 $('#add_perm').on('click', function(e){
129 129 addNewPermInput($(this), 'usergroup');
130 130 });
131 131 $('.revoke_perm').on('click', function(e){
132 132 markRevokePermInput($(this), 'usergroup');
133 133 });
134 134 </script>
@@ -1,186 +1,186 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 <div class="panel panel-default">
5 5 <div class="panel-heading">
6 6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
7 7 </div>
8 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 10 <div class="form">
11 11 <!-- fields -->
12 12 <div class="fields">
13 13 <div class="field">
14 14 <div class="label">
15 15 <label for="users_group_name">${_('Group name')}:</label>
16 16 </div>
17 17 <div class="input">
18 18 ${h.text('users_group_name',class_='medium')}
19 19 </div>
20 20 </div>
21 21
22 22 <div class="field badged-field">
23 23 <div class="label">
24 24 <label for="user">${_('Owner')}:</label>
25 25 </div>
26 26 <div class="input">
27 27 <div class="badge-input-container">
28 28 <div class="user-badge">
29 29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 30 </div>
31 31 <div class="badge-input-wrap">
32 32 ${h.text('user', class_="medium", autocomplete="off")}
33 33 </div>
34 34 </div>
35 35 <form:error name="user"/>
36 36 <p class="help-block">${_('Change owner of this user group.')}</p>
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-textarea">
42 42 <label for="user_group_description">${_('Description')}:</label>
43 43 </div>
44 44 <div class="textarea textarea-small editor">
45 45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
46 46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 47 </div>
48 48 </div>
49 49 <div class="field">
50 50 <div class="label label-checkbox">
51 51 <label for="users_group_active">${_('Active')}:</label>
52 52 </div>
53 53 <div class="checkboxes">
54 54 ${h.checkbox('users_group_active',value=True)}
55 55 </div>
56 56 </div>
57 57
58 58 <div class="field">
59 59 <div class="label label-checkbox">
60 60 <label for="users_group_active">${_('Add members')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
64 64 </div>
65 65 </div>
66 66
67 67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
68 68 <table id="group_members_placeholder" class="rctable group_members">
69 69 <tr>
70 70 <th>${_('Username')}</th>
71 71 <th>${_('Action')}</th>
72 72 </tr>
73 73
74 74 % if c.group_members_obj:
75 75 % for user in c.group_members_obj:
76 76 <tr>
77 77 <td id="member_user_${user.user_id}" class="td-author">
78 78 <div class="group_member">
79 79 ${base.gravatar(user.email, 16)}
80 80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
81 81 <input type="hidden" name="__start__" value="member:mapping">
82 82 <input type="hidden" name="member_user_id" value="${user.user_id}">
83 83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
84 84 <input type="hidden" name="__end__" value="member:mapping">
85 85 </div>
86 86 </td>
87 87 <td class="">
88 88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
89 89 <i class="icon-remove-sign"></i>
90 90 </div>
91 91 </td>
92 92 </tr>
93 93 % endfor
94 94
95 95 % else:
96 96 <tr><td colspan="2">${_('No members yet')}</td></tr>
97 97 % endif
98 98 </table>
99 99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
100 100
101 101 <div class="buttons">
102 102 ${h.submit('Save',_('Save'),class_="btn")}
103 103 </div>
104 104 </div>
105 105 </div>
106 106 ${h.end_form()}
107 107 </div>
108 108 </div>
109 109 <script>
110 110 $(document).ready(function(){
111 111 $("#group_parent_id").select2({
112 112 'containerCssClass': "drop-menu",
113 113 'dropdownCssClass': "drop-menu-dropdown",
114 114 'dropdownAutoWidth': true
115 115 });
116 116
117 117 removeUserGroupMember = function(userId){
118 118 $('#member_'+userId).val('remove');
119 119 $('#member_user_'+userId).addClass('to-delete');
120 120 };
121 121
122 122 $('#user_group_add_members').autocomplete({
123 123 serviceUrl: pyroutes.url('user_autocomplete_data'),
124 124 minChars:2,
125 125 maxHeight:400,
126 126 width:300,
127 127 deferRequestBy: 300, //miliseconds
128 128 showNoSuggestionNotice: true,
129 129 params: { user_groups:true },
130 130 formatResult: autocompleteFormatResult,
131 131 lookupFilter: autocompleteFilterResult,
132 132 onSelect: function(element, suggestion){
133 133
134 134 function addMember(user, fromUserGroup) {
135 135 var gravatar = user.icon_link;
136 136 var username = user.value_display;
137 137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
138 138 var uid = user.id;
139 139
140 140 if (fromUserGroup) {
141 141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
142 142 }
143 143
144 144 var elem = $(
145 145 ('<tr>'+
146 146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
147 147 '<div class="group_member">'+
148 148 '<img class="gravatar" src="{0}" height="16" width="16">'+
149 149 '<span class="username user"><a href="{1}">{2}</a></span>'+
150 150 '<input type="hidden" name="__start__" value="member:mapping">'+
151 151 '<input type="hidden" name="member_user_id" value="{3}">'+
152 152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
153 153 '<input type="hidden" name="__end__" value="member:mapping">'+
154 154 '</div>'+
155 155 '</td>'+
156 156 '<td class="td-author-new-entry">'+
157 157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
158 158 '<i class="icon-remove-sign"></i>'+
159 159 '</div>'+
160 160 '</td>'+
161 161 '</tr>').format(gravatar, userLink, username,
162 162 uid, uid, uid, uid)
163 163 );
164 164 $('#group_members_placeholder').append(elem)
165 165 }
166 166
167 167 if (suggestion.value_type == 'user_group') {
168 168 $.getJSON(
169 169 pyroutes.url('user_group_members_data',
170 170 {'user_group_id': suggestion.id}),
171 171 function(data) {
172 172 $.each(data.members, function(idx, user) {
173 173 addMember(user, suggestion.value)
174 174 });
175 175 }
176 176 );
177 177 } else if (suggestion.value_type == 'user') {
178 178 addMember(suggestion, null);
179 179 }
180 180 }
181 181 });
182 182
183 183
184 184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
185 185 })
186 186 </script>
@@ -1,108 +1,108 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('User groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 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 29 </li>
30 30 %endif
31 31 </ul>
32 32 </div>
33 33
34 34 <div id="repos_list_wrap">
35 35 <table id="user_group_list_table" class="display"></table>
36 36 </div>
37 37
38 38 </div>
39 39 <script>
40 40 $(document).ready(function() {
41 41 var getDatatableCount = function(){
42 42 var table = $('#user_group_list_table').dataTable();
43 43 var page = table.api().page.info();
44 44 var active = page.recordsDisplay;
45 45 var total = page.recordsTotal;
46 46
47 47 var _text = _gettext("{0} out of {1} users").format(active, total);
48 48 $('#user_group_count').text(_text);
49 49 };
50 50
51 51 // user list
52 52 $('#user_group_list_table').DataTable({
53 53 processing: true,
54 54 serverSide: true,
55 55 ajax: "${h.route_path('user_groups_data')}",
56 56 dom: 'rtp',
57 57 pageLength: ${c.visual.admin_grid_items},
58 58 order: [[ 0, "asc" ]],
59 59 columns: [
60 60 { data: {"_": "users_group_name",
61 61 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
62 62 { data: {"_": "description",
63 63 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
64 64 { data: {"_": "members",
65 65 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
66 66 { data: {"_": "sync",
67 67 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
68 68 { data: {"_": "active",
69 69 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
70 70 { data: {"_": "owner",
71 71 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
72 72 { data: {"_": "action",
73 73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
74 74 ],
75 75 language: {
76 76 paginate: DEFAULT_GRID_PAGINATION,
77 77 sProcessing: _gettext('loading...'),
78 78 emptyTable: _gettext("No user groups available yet.")
79 79 }
80 80 });
81 81
82 82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
83 83 $('#user_group_list_table').css('opacity', 1);
84 84 });
85 85
86 86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
87 87 $('#user_group_list_table').css('opacity', 0.3);
88 88 });
89 89
90 90 // refresh counters on draw
91 91 $('#user_group_list_table').on('draw.dt', function(){
92 92 getDatatableCount();
93 93 });
94 94
95 95 // filter
96 96 $('#q_filter').on('keyup',
97 97 $.debounce(250, function() {
98 98 $('#user_group_list_table').DataTable().search(
99 99 $('#q_filter').val()
100 100 ).draw();
101 101 })
102 102 );
103 103
104 104 });
105 105
106 106 </script>
107 107
108 108 </%def>
@@ -1,609 +1,609 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 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 80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 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 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 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 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 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 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 232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
233 233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
234 234 <li class="${is_active('showpullrequest')}">
235 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 236 %if c.repository_pull_requests:
237 237 <span class="pr_notifications">${c.repository_pull_requests}</span>
238 238 %endif
239 239 <div class="menulabel">${_('Pull Requests')}</div>
240 240 </a>
241 241 </li>
242 242 %endif
243 243 <li class="${is_active('options')}">
244 244 <a class="menulink dropdown">
245 245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
246 246 </a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li>
253 253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 254 href="${h.route_path('repo_compare',
255 255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 260 _query=dict(merge=1))}"
261 261 >
262 262 ${_('Compare fork')}
263 263 </a>
264 264 </li>
265 265 %endif
266 266
267 267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
268 268
269 269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
270 270 %if c.rhodecode_db_repo.locked[0]:
271 271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
272 272 %else:
273 273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
274 274 %endif
275 275 %endif
276 276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 280 %endif
281 281 %endif
282 282 </ul>
283 283 </li>
284 284 </ul>
285 285 </div>
286 286 <div class="clear"></div>
287 287 </div>
288 288 <!--- END CONTEXT BAR -->
289 289
290 290 </%def>
291 291
292 292 <%def name="usermenu(active=False)">
293 293 ## USER MENU
294 294 <li id="quick_login_li" class="${'active' if active else ''}">
295 295 <a id="quick_login_link" class="menulink childs">
296 296 ${gravatar(c.rhodecode_user.email, 20)}
297 297 <span class="user">
298 298 %if c.rhodecode_user.username != h.DEFAULT_USER:
299 299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
300 300 %else:
301 301 <span>${_('Sign in')}</span>
302 302 %endif
303 303 </span>
304 304 </a>
305 305
306 306 <div class="user-menu submenu">
307 307 <div id="quick_login">
308 308 %if c.rhodecode_user.username == h.DEFAULT_USER:
309 309 <h4>${_('Sign in to your account')}</h4>
310 310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
311 311 <div class="form form-vertical">
312 312 <div class="fields">
313 313 <div class="field">
314 314 <div class="label">
315 315 <label for="username">${_('Username')}:</label>
316 316 </div>
317 317 <div class="input">
318 318 ${h.text('username',class_='focus',tabindex=1)}
319 319 </div>
320 320
321 321 </div>
322 322 <div class="field">
323 323 <div class="label">
324 324 <label for="password">${_('Password')}:</label>
325 325 %if h.HasPermissionAny('hg.password_reset.enabled')():
326 326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
327 327 %endif
328 328 </div>
329 329 <div class="input">
330 330 ${h.password('password',class_='focus',tabindex=2)}
331 331 </div>
332 332 </div>
333 333 <div class="buttons">
334 334 <div class="register">
335 335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
336 336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
337 337 %endif
338 338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
339 339 </div>
340 340 <div class="submit">
341 341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
342 342 </div>
343 343 </div>
344 344 </div>
345 345 </div>
346 346 ${h.end_form()}
347 347 %else:
348 348 <div class="">
349 349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
350 350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
351 351 <div class="email">${c.rhodecode_user.email}</div>
352 352 </div>
353 353 <div class="">
354 354 <ol class="links">
355 355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
356 356 % if c.rhodecode_user.personal_repo_group:
357 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 358 % endif
359 359 <li class="logout">
360 360 ${h.secure_form(h.route_path('logout'), request=request)}
361 361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
362 362 ${h.end_form()}
363 363 </li>
364 364 </ol>
365 365 </div>
366 366 %endif
367 367 </div>
368 368 </div>
369 369 %if c.rhodecode_user.username != h.DEFAULT_USER:
370 370 <div class="pill_container">
371 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 372 </div>
373 373 % endif
374 374 </li>
375 375 </%def>
376 376
377 377 <%def name="menu_items(active=None)">
378 378 <%
379 379 def is_active(selected):
380 380 if selected == active:
381 381 return "active"
382 382 return ""
383 383 %>
384 384 <ul id="quick" class="main_nav navigation horizontal-list">
385 385 <!-- repo switcher -->
386 386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
387 387 <input id="repo_switcher" name="repo_switcher" type="hidden">
388 388 </li>
389 389
390 390 ## ROOT MENU
391 391 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 392 <li class="${is_active('journal')}">
393 393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
394 394 <div class="menulabel">${_('Journal')}</div>
395 395 </a>
396 396 </li>
397 397 %else:
398 398 <li class="${is_active('journal')}">
399 399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
400 400 <div class="menulabel">${_('Public journal')}</div>
401 401 </a>
402 402 </li>
403 403 %endif
404 404 <li class="${is_active('gists')}">
405 405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
406 406 <div class="menulabel">${_('Gists')}</div>
407 407 </a>
408 408 </li>
409 409 <li class="${is_active('search')}">
410 410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
411 411 <div class="menulabel">${_('Search')}</div>
412 412 </a>
413 413 </li>
414 414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
415 415 <li class="${is_active('admin')}">
416 416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
417 417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 418 </a>
419 419 ${admin_menu()}
420 420 </li>
421 421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
422 422 <li class="${is_active('admin')}">
423 423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
424 424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
425 425 </a>
426 426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
427 427 c.rhodecode_user.repository_groups_admin,
428 428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
429 429 </li>
430 430 % endif
431 431 % if c.debug_style:
432 432 <li class="${is_active('debug_style')}">
433 433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
434 434 <div class="menulabel">${_('Style')}</div>
435 435 </a>
436 436 </li>
437 437 % endif
438 438 ## render extra user menu
439 439 ${usermenu(active=(active=='my_account'))}
440 440 </ul>
441 441
442 442 <script type="text/javascript">
443 443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
444 444
445 445 /*format the look of items in the list*/
446 446 var format = function(state, escapeMarkup){
447 447 if (!state.id){
448 448 return state.text; // optgroup
449 449 }
450 450 var obj_dict = state.obj;
451 451 var tmpl = '';
452 452
453 453 if(obj_dict && state.type == 'repo'){
454 454 if(obj_dict['repo_type'] === 'hg'){
455 455 tmpl += '<i class="icon-hg"></i> ';
456 456 }
457 457 else if(obj_dict['repo_type'] === 'git'){
458 458 tmpl += '<i class="icon-git"></i> ';
459 459 }
460 460 else if(obj_dict['repo_type'] === 'svn'){
461 461 tmpl += '<i class="icon-svn"></i> ';
462 462 }
463 463 if(obj_dict['private']){
464 464 tmpl += '<i class="icon-lock" ></i> ';
465 465 }
466 466 else if(visual_show_public_icon){
467 467 tmpl += '<i class="icon-unlock-alt"></i> ';
468 468 }
469 469 }
470 470 if(obj_dict && state.type == 'commit') {
471 471 tmpl += '<i class="icon-tag"></i>';
472 472 }
473 473 if(obj_dict && state.type == 'group'){
474 474 tmpl += '<i class="icon-folder-close"></i> ';
475 475 }
476 476 tmpl += escapeMarkup(state.text);
477 477 return tmpl;
478 478 };
479 479
480 480 var formatResult = function(result, container, query, escapeMarkup) {
481 481 return format(result, escapeMarkup);
482 482 };
483 483
484 484 var formatSelection = function(data, container, escapeMarkup) {
485 485 return format(data, escapeMarkup);
486 486 };
487 487
488 488 $("#repo_switcher").select2({
489 489 cachedDataSource: {},
490 490 minimumInputLength: 2,
491 491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
492 492 dropdownAutoWidth: true,
493 493 formatResult: formatResult,
494 494 formatSelection: formatSelection,
495 495 containerCssClass: "repo-switcher",
496 496 dropdownCssClass: "repo-switcher-dropdown",
497 497 escapeMarkup: function(m){
498 498 // don't escape our custom placeholder
499 499 if(m.substr(0,23) == '<div class="menulabel">'){
500 500 return m;
501 501 }
502 502
503 503 return Select2.util.escapeMarkup(m);
504 504 },
505 505 query: $.debounce(250, function(query){
506 506 self = this;
507 507 var cacheKey = query.term;
508 508 var cachedData = self.cachedDataSource[cacheKey];
509 509
510 510 if (cachedData) {
511 511 query.callback({results: cachedData.results});
512 512 } else {
513 513 $.ajax({
514 514 url: pyroutes.url('goto_switcher_data'),
515 515 data: {'query': query.term},
516 516 dataType: 'json',
517 517 type: 'GET',
518 518 success: function(data) {
519 519 self.cachedDataSource[cacheKey] = data;
520 520 query.callback({results: data.results});
521 521 },
522 522 error: function(data, textStatus, errorThrown) {
523 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 530 $("#repo_switcher").on('select2-selecting', function(e){
531 531 e.preventDefault();
532 532 window.location = e.choice.url;
533 533 });
534 534
535 535 </script>
536 536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
537 537 </%def>
538 538
539 539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
540 540 <div class="modal-dialog">
541 541 <div class="modal-content">
542 542 <div class="modal-header">
543 543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
544 544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
545 545 </div>
546 546 <div class="modal-body">
547 547 <div class="block-left">
548 548 <table class="keyboard-mappings">
549 549 <tbody>
550 550 <tr>
551 551 <th></th>
552 552 <th>${_('Site-wide shortcuts')}</th>
553 553 </tr>
554 554 <%
555 555 elems = [
556 556 ('/', 'Open quick search box'),
557 557 ('g h', 'Goto home page'),
558 558 ('g g', 'Goto my private gists page'),
559 559 ('g G', 'Goto my public gists page'),
560 560 ('n r', 'New repository page'),
561 561 ('n g', 'New gist page'),
562 562 ]
563 563 %>
564 564 %for key, desc in elems:
565 565 <tr>
566 566 <td class="keys">
567 567 <span class="key tag">${key}</span>
568 568 </td>
569 569 <td>${desc}</td>
570 570 </tr>
571 571 %endfor
572 572 </tbody>
573 573 </table>
574 574 </div>
575 575 <div class="block-left">
576 576 <table class="keyboard-mappings">
577 577 <tbody>
578 578 <tr>
579 579 <th></th>
580 580 <th>${_('Repositories')}</th>
581 581 </tr>
582 582 <%
583 583 elems = [
584 584 ('g s', 'Goto summary page'),
585 585 ('g c', 'Goto changelog page'),
586 586 ('g f', 'Goto files page'),
587 587 ('g F', 'Goto files page with file search activated'),
588 588 ('g p', 'Goto pull requests page'),
589 589 ('g o', 'Goto repository settings'),
590 590 ('g O', 'Goto repository permissions settings'),
591 591 ]
592 592 %>
593 593 %for key, desc in elems:
594 594 <tr>
595 595 <td class="keys">
596 596 <span class="key tag">${key}</span>
597 597 </td>
598 598 <td>${desc}</td>
599 599 </tr>
600 600 %endfor
601 601 </tbody>
602 602 </table>
603 603 </div>
604 604 </div>
605 605 <div class="modal-footer">
606 606 </div>
607 607 </div><!-- /.modal-content -->
608 608 </div><!-- /.modal-dialog -->
609 609 </div><!-- /.modal -->
@@ -1,156 +1,156 b''
1 1 ## snippet for displaying default permission box
2 2 ## usage:
3 3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
4 4 ## ${dpb.default_perms_box(<url_to_form>)}
5 5 ## ${dpb.default_perms_radios()}
6 6
7 7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
8 8 <div class="main-content-full-width">
9 9 <div class="panel panel-default">
10 10
11 11 ## displayed according to checkbox selection
12 12 <div class="panel-heading">
13 13 %if not global_permissions_template:
14 14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
15 15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
16 16 %else:
17 17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
18 18 %endif
19 19 </div>
20 20
21 21 <div class="panel-body">
22 22 %if global_permissions_template:
23 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 24 %endif
25 25 <div class="field">
26 26 <div class="label">
27 27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
28 28 </div>
29 29 <div class="radios">
30 30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
31 31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
32 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 33 </div>
34 34 </div>
35 35 <div class="field">
36 36 <div class="label">
37 37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
38 38 </div>
39 39 <div class="radios">
40 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 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 42 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
43 43 </div>
44 44 </div>
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
48 48 </div>
49 49 <div class="radios">
50 50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
51 51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
52 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 53 </div>
54 54 </div>
55 55 <div class="field">
56 56 <div class="label">
57 57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
58 58 </div>
59 59 <div class="radios">
60 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 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 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 63 </div>
64 64 </div>
65 65 <div class="field">
66 66 <div class="label">
67 67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
68 68 </div>
69 69 <div class="radios">
70 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 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 72 <span class="help-block">${_('Permission to allow user group creation.')}</span>
73 73 </div>
74 74 </div>
75 75
76 76 <div class="field">
77 77 <div class="label">
78 78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
79 79 </div>
80 80 <div class="radios">
81 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 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 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 84 </div>
85 85 </div>
86 86
87 87 <div class="buttons">
88 88 ${h.submit('save',_('Save'),class_="btn")}
89 89 ${h.reset('reset',_('Reset'),class_="btn")}
90 90 </div>
91 91 </div>
92 92 </div>
93 93 </div>
94 94 </%def>
95 95
96 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 98 <div class="form">
99 99 <div class="fields">
100 100 <div class="field panel panel-default panel-body">
101 101 <div class="label label-checkbox">
102 102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
103 103 </div>
104 104 <div class="checkboxes">
105 105 ${h.checkbox('inherit_default_permissions',value=True)}
106 106 <span class="help-block">
107 107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
108 108 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
109 109 % h.link_to('default user', h.route_path('admin_permissions_global')))}
110 110 </span>
111 111 </div>
112 112 </div>
113 113
114 114 ## INHERITED permissions == the user permissions in admin
115 115 ## if inherit checkbox is set this is displayed in non-edit mode
116 116 <div class="inherit_overlay_default">
117 117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
118 118 </div>
119 119
120 120 ## CUSTOM permissions
121 121 <div class="inherit_overlay">
122 122 ${default_perms_radios(global_permissions_template = False)}
123 123 </div>
124 124 </div>
125 125 </div>
126 126 ${h.end_form()}
127 127
128 128
129 129 ## JS
130 130 <script>
131 131 var show_custom_perms = function(inherit_default){
132 132 if(inherit_default) {
133 133 $('.inherit_overlay_default').show();
134 134 $('.inherit_overlay').hide();
135 135 }
136 136 else {
137 137 $('.inherit_overlay').show();
138 138 $('.inherit_overlay_default').hide();
139 139 }
140 140 };
141 141 $(document).ready(function(e){
142 142 var inherit_checkbox = $('#inherit_default_permissions');
143 143 var defaults = inherit_checkbox.prop('checked');
144 144 show_custom_perms(defaults);
145 145 inherit_checkbox.on('change', function(){
146 146 if($(this).prop('checked')){
147 147 show_custom_perms(true);
148 148 }
149 149 else{
150 150 show_custom_perms(false);
151 151 }
152 152 })
153 153 })
154 154 </script>
155 155
156 156 </%def>
@@ -1,268 +1,268 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 7 <div id="perms" class="table fields">
8 8 %for section in sorted(permissions.keys()):
9 9 <div class="panel panel-default">
10 10 <div class="panel-heading">
11 11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 12 % if side_link:
13 13 <div class="pull-right">
14 14 <a href="${side_link}">${_('in JSON format')}</a>
15 15 </div>
16 16 % endif
17 17 </div>
18 18 <div class="panel-body">
19 19 <div class="perms_section_head field">
20 20 <div class="radios">
21 21 %if section != 'global':
22 22 <span class="permissions_boxes">
23 23 <span class="desc">${_('show')}: </span>
24 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 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 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 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 28 </span>
29 29 %endif
30 30 </div>
31 31 </div>
32 32 <div class="field">
33 33 %if not permissions[section]:
34 34 <p class="empty_data help-block">${_('No permissions defined')}</p>
35 35 %else:
36 36 <div id='tbl_list_wrap_${section}'>
37 37 <table id="tbl_list_${section}" class="rctable">
38 38 ## global permission box
39 39 %if section == 'global':
40 40 <thead>
41 41 <tr>
42 42 <th colspan="2" class="left">${_('Permission')}</th>
43 43 %if actions:
44 44 <th colspan="2">${_('Edit Permission')}</th>
45 45 %endif
46 46 </thead>
47 47 <tbody>
48 48
49 49 <%
50 50 def get_section_perms(prefix, opts):
51 51 _selected = []
52 52 for op in opts:
53 53 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
54 54 _selected.append(op)
55 55 admin = 'hg.admin' in opts
56 56 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
57 57 return admin, _selected_vals, _selected
58 58 %>
59 59
60 60 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
61 61 <tr>
62 62 <td class="td-tags">
63 63 ${lbl}
64 64 </td>
65 65 <td class="td-tags">
66 66 %if val[0]:
67 67 %if not val_lbl:
68 68 ## super admin case
69 69 True
70 70 %else:
71 71 <span class="perm_tag admin">${val_lbl}.admin</span>
72 72 %endif
73 73 %else:
74 74 %if not val_lbl:
75 75 ${
76 76 {'false': False,
77 77 'true': True,
78 78 'none': False,
79 79 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')
80 80 }
81 81 %else:
82 82 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
83 83 %endif
84 84 %endif
85 85 </td>
86 86 %if actions:
87 87
88 88 % if edit_url or edit_global_url:
89 89
90 90 <td class="td-action">
91 91 % if edit_url:
92 92 <a href="${edit_url}">${_('edit')}</a>
93 93 % else:
94 94 -
95 95 % endif
96 96 </td>
97 97
98 98 <td class="td-action">
99 99 % if edit_global_url:
100 100 <a href="${edit_global_url}">${_('edit global')}</a>
101 101 % else:
102 102 -
103 103 % endif
104 104 </td>
105 105
106 106 % else:
107 107 <td class="td-action"></td>
108 108 <td class="td-action">
109 109 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
110 110 <td class="td-action">
111 111 % endif
112 112
113 113 %endif
114 114 </tr>
115 115 </%def>
116 116
117 117 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
118 118 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
119 119
120 120 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
121 121 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
122 122
123 123 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
124 124 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
125 125
126 126 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]),
127 127 edit_url=h.url('edit_user', user_id=c.user.user_id, anchor='admin'), edit_global_url=None)}
128 128
129 129 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
130 130 edit_url=h.url('edit_user_global_perms', user_id=c.user.user_id), edit_global_url=None)}
131 131
132 132 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
133 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 135 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
136 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 138 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
139 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 141 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
142 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 144 </tbody>
145 145 %else:
146 146 ## none/read/write/admin permissions on groups/repos etc
147 147 <thead>
148 148 <tr>
149 149 <th>${_('Name')}</th>
150 150 <th>${_('Permission')}</th>
151 151 %if actions:
152 152 <th>${_('Edit Permission')}</th>
153 153 %endif
154 154 </thead>
155 155 <tbody class="section_${section}">
156 156 <%
157 157 def sorter(permissions):
158 158 def custom_sorter(item):
159 159 ## read/write/admin
160 160 section = item[1].split('.')[-1]
161 161 section_importance = {'none': u'0',
162 162 'read': u'1',
163 163 'write':u'2',
164 164 'admin':u'3'}.get(section)
165 165 ## sort by group importance+name
166 166 return section_importance+item[0]
167 167 return sorted(permissions, key=custom_sorter)
168 168 %>
169 169 %for k, section_perm in sorter(permissions[section].items()):
170 170 %if section_perm.split('.')[-1] != 'none' or show_all:
171 171 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
172 172 <td class="td-name">
173 173 %if section == 'repositories':
174 174 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
175 175 %elif section == 'repositories_groups':
176 176 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
177 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 179 ${k}
180 180 %endif
181 181 </td>
182 182 <td class="td-tags">
183 183 %if hasattr(permissions[section], 'perm_origin_stack'):
184 184 <div>
185 185 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
186 186
187 187 % if i > 0:
188 188 <div style="color: #979797">
189 189 <i class="icon-arrow_up"></i>
190 190 ${_('overridden by')}
191 191 <i class="icon-arrow_up"></i>
192 192 </div>
193 193 % endif
194 194
195 195 <div>
196 196 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
197 197 ${perm} (${origin})
198 198 </span>
199 199 </div>
200 200
201 201 %endfor
202 202 </div>
203 203 %else:
204 204 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
205 205 %endif
206 206 </td>
207 207 %if actions:
208 208 <td class="td-action">
209 209 %if section == 'repositories':
210 210 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
211 211 %elif section == 'repositories_groups':
212 212 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
213 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 215 %endif
216 216 </td>
217 217 %endif
218 218 </tr>
219 219 %endif
220 220 %endfor
221 221
222 222 <tr id="empty_${section}" class="noborder" style="display:none;">
223 223 <td colspan="6">${_('No permission defined')}</td>
224 224 </tr>
225 225
226 226 </tbody>
227 227 %endif
228 228 </table>
229 229 </div>
230 230 %endif
231 231 </div>
232 232 </div>
233 233 </div>
234 234 %endfor
235 235 </div>
236 236
237 237 <script>
238 238 $(document).ready(function(){
239 239 var show_empty = function(section){
240 240 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
241 241 if(visible == 0){
242 242 $('#empty_{0}'.format(section)).show();
243 243 }
244 244 else{
245 245 $('#empty_{0}'.format(section)).hide();
246 246 }
247 247 };
248 248 $('.perm_filter').on('change', function(e){
249 249 var self = this;
250 250 var section = $(this).attr('section');
251 251
252 252 var opts = {};
253 253 var elems = $('.filter_' + section).each(function(el){
254 254 var perm_type = $(this).attr('perm_type');
255 255 var checked = this.checked;
256 256 opts[perm_type] = checked;
257 257 if(checked){
258 258 $('.'+section+'_'+perm_type).show();
259 259 }
260 260 else{
261 261 $('.'+section+'_'+perm_type).hide();
262 262 }
263 263 });
264 264 show_empty(section);
265 265 })
266 266 })
267 267 </script>
268 268 </%def>
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
General Comments 0
You need to be logged in to leave comments. Login now