diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py --- a/rhodecode/apps/_base/__init__.py +++ b/rhodecode/apps/_base/__init__.py @@ -260,6 +260,20 @@ class RepoGroupAppView(BaseAppView): self.db_repo_group = request.db_repo_group self.db_repo_group_name = self.db_repo_group.group_name + def _revoke_perms_on_yourself(self, form_result): + _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), + form_result['perm_updates']) + _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), + form_result['perm_additions']) + _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), + form_result['perm_deletions']) + admin_perm = 'group.admin' + if _updates and _updates[0][1] != admin_perm or \ + _additions and _additions[0][1] != admin_perm or \ + _deletions and _deletions[0][1] != admin_perm: + return True + return False + class UserGroupAppView(BaseAppView): def __init__(self, context, request): diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py --- a/rhodecode/apps/admin/__init__.py +++ b/rhodecode/apps/admin/__init__.py @@ -295,6 +295,19 @@ def admin_routes(config): name='repo_create', pattern='/repos/create') + # repo groups admin + config.add_route( + name='repo_groups', + pattern='/repo_groups') + + config.add_route( + name='repo_group_new', + pattern='/repo_group/new') + + config.add_route( + name='repo_group_create', + pattern='/repo_group/create') + def includeme(config): settings = config.get_settings() diff --git a/rhodecode/apps/admin/tests/test_admin_repository_groups.py b/rhodecode/apps/admin/tests/test_admin_repository_groups.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/admin/tests/test_admin_repository_groups.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import os +import pytest + +from rhodecode.apps._base import ADMIN_PREFIX +from rhodecode.lib import helpers as h +from rhodecode.model.db import Repository, UserRepoToPerm, User +from rhodecode.model.meta import Session +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.tests import ( + assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, TestController) +from rhodecode.tests.fixture import Fixture + +fixture = Fixture() + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_groups': ADMIN_PREFIX + '/repo_groups', + 'repo_group_new': ADMIN_PREFIX + '/repo_group/new', + 'repo_group_create': ADMIN_PREFIX + '/repo_group/create', + + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +def _get_permission_for_user(user, repo): + perm = UserRepoToPerm.query()\ + .filter(UserRepoToPerm.repository == + Repository.get_by_repo_name(repo))\ + .filter(UserRepoToPerm.user == User.get_by_username(user))\ + .all() + return perm + + +@pytest.mark.usefixtures("app") +class TestAdminRepositoryGroups(object): + def test_show_repo_groups(self, autologin_user): + response = self.app.get(route_path('repo_groups')) + response.mustcontain('data: []') + + def test_show_repo_groups_after_creating_group(self, autologin_user): + fixture.create_repo_group('test_repo_group') + response = self.app.get(route_path('repo_groups')) + response.mustcontain('"name_raw": "test_repo_group"') + fixture.destroy_repo_group('test_repo_group') + + def test_new(self, autologin_user): + self.app.get(route_path('repo_group_new')) + + def test_new_with_parent_group(self, autologin_user, user_util): + gr = user_util.create_repo_group() + + self.app.get(route_path('repo_group_new'), + params=dict(parent_group=gr.group_name)) + + def test_new_by_regular_user_no_permission(self, autologin_regular_user): + self.app.get(route_path('repo_group_new'), status=403) + + @pytest.mark.parametrize('repo_group_name', [ + 'git_repo', + 'git_repo_ąć', + 'hg_repo', + '12345', + 'hg_repo_ąć', + ]) + def test_create(self, autologin_user, repo_group_name, csrf_token): + repo_group_name_unicode = repo_group_name.decode('utf8') + description = 'description for newly created repo group' + + response = self.app.post( + route_path('repo_group_create'), + fixture._get_group_create_params( + group_name=repo_group_name, + group_description=description, + csrf_token=csrf_token)) + + # run the check page that triggers the flash message + repo_gr_url = h.route_path( + 'repo_group_home', repo_group_name=repo_group_name) + + assert_session_flash( + response, + 'Created repository group %s' % ( + repo_gr_url, repo_group_name_unicode)) + + # # test if the repo group was created in the database + new_repo_group = RepoGroupModel()._get_repo_group( + repo_group_name_unicode) + assert new_repo_group is not None + + assert new_repo_group.group_name == repo_group_name_unicode + assert new_repo_group.group_description == description + + # test if the repository is visible in the list ? + response = self.app.get(repo_gr_url) + response.mustcontain(repo_group_name) + + # test if the repository group was created on filesystem + is_on_filesystem = os.path.isdir( + os.path.join(TESTS_TMP_PATH, repo_group_name)) + if not is_on_filesystem: + self.fail('no repo group %s in filesystem' % repo_group_name) + + RepoGroupModel().delete(repo_group_name_unicode) + Session().commit() + + @pytest.mark.parametrize('repo_group_name', [ + 'git_repo', + 'git_repo_ąć', + 'hg_repo', + '12345', + 'hg_repo_ąć', + ]) + def test_create_subgroup(self, autologin_user, user_util, repo_group_name, csrf_token): + parent_group = user_util.create_repo_group() + parent_group_name = parent_group.group_name + + expected_group_name = '{}/{}'.format( + parent_group_name, repo_group_name) + expected_group_name_unicode = expected_group_name.decode('utf8') + + try: + response = self.app.post( + route_path('repo_group_create'), + fixture._get_group_create_params( + group_name=repo_group_name, + group_parent_id=parent_group.group_id, + group_description='Test desciption', + csrf_token=csrf_token)) + + assert_session_flash( + response, + u'Created repository group %s' % ( + h.route_path('repo_group_home', + repo_group_name=expected_group_name), + expected_group_name_unicode)) + finally: + RepoGroupModel().delete(expected_group_name_unicode) + Session().commit() + + def test_user_with_creation_permissions_cannot_create_subgroups( + self, autologin_regular_user, user_util): + + user_util.grant_user_permission( + TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true') + parent_group = user_util.create_repo_group() + parent_group_id = parent_group.group_id + self.app.get( + route_path('repo_group_new', + params=dict(parent_group=parent_group_id), ), + status=403) diff --git a/rhodecode/apps/admin/views/repo_groups.py b/rhodecode/apps/admin/views/repo_groups.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/admin/views/repo_groups.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging +import formencode +import formencode.htmlfill + +from pyramid.httpexceptions import HTTPFound, HTTPForbidden +from pyramid.view import view_config +from pyramid.renderers import render +from pyramid.response import Response + +from rhodecode.apps._base import BaseAppView, DataGridAppView + +from rhodecode.lib.ext_json import json +from rhodecode.lib.auth import ( + LoginRequired, CSRFRequired, NotAnonymous, + HasPermissionAny, HasRepoGroupPermissionAny) +from rhodecode.lib import helpers as h, audit_logger +from rhodecode.lib.utils2 import safe_int, safe_unicode +from rhodecode.model.forms import RepoGroupForm +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.scm import RepoGroupList +from rhodecode.model.db import Session, RepoGroup + +log = logging.getLogger(__name__) + + +class AdminRepoGroupsView(BaseAppView, DataGridAppView): + + def load_default_context(self): + c = self._get_local_tmpl_context() + self._register_global_c(c) + return c + + def _load_form_data(self, c): + allow_empty_group = False + + if self._can_create_repo_group(): + # we're global admin, we're ok and we can create TOP level groups + allow_empty_group = True + + # override the choices for this form, we need to filter choices + # and display only those we have ADMIN right + groups_with_admin_rights = RepoGroupList( + RepoGroup.query().all(), + perm_set=['group.admin']) + c.repo_groups = RepoGroup.groups_choices( + groups=groups_with_admin_rights, + show_empty_group=allow_empty_group) + + def _can_create_repo_group(self, parent_group_id=None): + is_admin = HasPermissionAny('hg.admin')('group create controller') + create_repo_group = HasPermissionAny( + 'hg.repogroup.create.true')('group create controller') + if is_admin or (create_repo_group and not parent_group_id): + # we're global admin, or we have global repo group create + # permission + # we're ok and we can create TOP level groups + return True + elif parent_group_id: + # we check the permission if we can write to parent group + group = RepoGroup.get(parent_group_id) + group_name = group.group_name if group else None + if HasRepoGroupPermissionAny('group.admin')( + group_name, 'check if user is an admin of group'): + # we're an admin of passed in group, we're ok. + return True + else: + return False + return False + + @LoginRequired() + @NotAnonymous() + # perms check inside + @view_config( + route_name='repo_groups', request_method='GET', + renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako') + def repo_group_list(self): + c = self.load_default_context() + + repo_group_list = RepoGroup.get_all_repo_groups() + repo_group_list_acl = RepoGroupList( + repo_group_list, perm_set=['group.admin']) + repo_group_data = RepoGroupModel().get_repo_groups_as_dict( + repo_group_list=repo_group_list_acl, admin=True) + c.data = json.dumps(repo_group_data) + return self._get_template_context(c) + + @LoginRequired() + @NotAnonymous() + # perm checks inside + @view_config( + route_name='repo_group_new', request_method='GET', + renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako') + def repo_group_new(self): + c = self.load_default_context() + + # perm check for admin, create_group perm or admin of parent_group + parent_group_id = safe_int(self.request.GET.get('parent_group')) + if not self._can_create_repo_group(parent_group_id): + raise HTTPForbidden() + + self._load_form_data(c) + + defaults = {} # Future proof for default of repo group + data = render( + 'rhodecode:templates/admin/repo_groups/repo_group_add.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=defaults, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + + @LoginRequired() + @NotAnonymous() + @CSRFRequired() + # perm checks inside + @view_config( + route_name='repo_group_create', request_method='POST', + renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako') + def repo_group_create(self): + c = self.load_default_context() + _ = self.request.translate + + parent_group_id = safe_int(self.request.POST.get('group_parent_id')) + can_create = self._can_create_repo_group(parent_group_id) + + self._load_form_data(c) + # permissions for can create group based on parent_id are checked + # here in the Form + available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups) + repo_group_form = RepoGroupForm(available_groups=available_groups, + can_create_in_root=can_create)() + + repo_group_name = self.request.POST.get('group_name') + try: + owner = self._rhodecode_user + form_result = repo_group_form.to_python(dict(self.request.POST)) + repo_group = RepoGroupModel().create( + group_name=form_result['group_name_full'], + group_description=form_result['group_description'], + owner=owner.user_id, + copy_permissions=form_result['group_copy_permissions'] + ) + Session().flush() + + repo_group_data = repo_group.get_api_data() + audit_logger.store_web( + 'repo_group.create', action_data={'data': repo_group_data}, + user=self._rhodecode_user) + + Session().commit() + + _new_group_name = form_result['group_name_full'] + + repo_group_url = h.link_to( + _new_group_name, + h.route_path('repo_group_home', repo_group_name=_new_group_name)) + h.flash(h.literal(_('Created repository group %s') + % repo_group_url), category='success') + + except formencode.Invalid as errors: + data = render( + 'rhodecode:templates/admin/repo_groups/repo_group_add.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + except Exception: + log.exception("Exception during creation of repository group") + h.flash(_('Error occurred during creation of repository group %s') + % repo_group_name, category='error') + raise HTTPFound(h.route_path('home')) + + raise HTTPFound( + h.route_path('repo_group_home', + repo_group_name=form_result['group_name_full'])) diff --git a/rhodecode/apps/repo_group/__init__.py b/rhodecode/apps/repo_group/__init__.py --- a/rhodecode/apps/repo_group/__init__.py +++ b/rhodecode/apps/repo_group/__init__.py @@ -22,7 +22,36 @@ from rhodecode.apps._base import add_rou def includeme(config): - # Summary + # Settings + config.add_route( + name='edit_repo_group', + pattern='/{repo_group_name:.*?[^/]}/_edit', + repo_group_route=True) + # update is POST on edit_repo_group + + # Settings advanced + config.add_route( + name='edit_repo_group_advanced', + pattern='/{repo_group_name:.*?[^/]}/_settings/advanced', + repo_group_route=True) + + config.add_route( + name='edit_repo_group_advanced_delete', + pattern='/{repo_group_name:.*?[^/]}/_settings/advanced/delete', + repo_group_route=True) + + # settings permissions + config.add_route( + name='edit_repo_group_perms', + pattern='/{repo_group_name:.*?[^/]}/_settings/permissions', + repo_group_route=True) + + config.add_route( + name='edit_repo_group_perms_update', + pattern='/{repo_group_name:.*?[^/]}/_settings/permissions/update', + repo_group_route=True) + + # Summary, NOTE(marcink): needs to be at the end for catch-all add_route_with_slash( config, name='repo_group_home', @@ -30,4 +59,3 @@ def includeme(config): # Scan module for configuration decorators. config.scan('.views', ignore='.tests') - diff --git a/rhodecode/apps/repo_group/tests/__init__.py b/rhodecode/apps/repo_group/tests/__init__.py --- a/rhodecode/apps/repo_group/tests/__init__.py +++ b/rhodecode/apps/repo_group/tests/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py b/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import pytest + +from rhodecode.tests import assert_session_flash + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'edit_repo_group_advanced': + '/{repo_group_name}/_settings/advanced', + 'edit_repo_group_advanced_delete': + '/{repo_group_name}/_settings/advanced/delete', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +@pytest.mark.usefixtures("app") +class TestRepoGroupsAdvancedView(object): + + @pytest.mark.parametrize('repo_group_name', [ + 'gro', + '12345', + ]) + def test_show_advanced_settings(self, autologin_user, user_util, repo_group_name): + user_util._test_name = repo_group_name + gr = user_util.create_repo_group() + self.app.get( + route_path('edit_repo_group_advanced', + repo_group_name=gr.group_name)) + + def test_show_advanced_settings_delete(self, autologin_user, user_util, + csrf_token): + gr = user_util.create_repo_group(auto_cleanup=False) + repo_group_name = gr.group_name + + params = dict( + csrf_token=csrf_token + ) + response = self.app.post( + route_path('edit_repo_group_advanced_delete', + repo_group_name=repo_group_name), params=params) + assert_session_flash( + response, 'Removed repository group `{}`'.format(repo_group_name)) + + def test_delete_not_possible_with_objects_inside(self, autologin_user, + repo_groups, csrf_token): + zombie_group, parent_group, child_group = repo_groups + + response = self.app.get( + route_path('edit_repo_group_advanced', + repo_group_name=parent_group.group_name)) + + response.mustcontain( + 'This repository group includes 1 children repository group') + + params = dict( + csrf_token=csrf_token + ) + response = self.app.post( + route_path('edit_repo_group_advanced_delete', + repo_group_name=parent_group.group_name), params=params) + + assert_session_flash( + response, 'This repository group contains 1 subgroup ' + 'and cannot be deleted') diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py b/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import pytest + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'edit_repo_group_perms': + '/{repo_group_name:}/_settings/permissions', + 'edit_repo_group_perms_update': + '/{repo_group_name}/_settings/permissions/update', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +@pytest.mark.usefixtures("app") +class TestRepoGroupsPermissionsView(object): + + def test_edit_repo_group_perms(self, user_util, autologin_user): + repo_group = user_util.create_repo_group() + self.app.get( + route_path('edit_repo_group_perms', + repo_group_name=repo_group.group_name), status=200) + + def test_update_permissions(self): + pass diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py b/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import pytest + +from rhodecode.tests import assert_session_flash + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'edit_repo_group': '/{repo_group_name}/_edit', + # Update is POST to the above url + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +@pytest.mark.usefixtures("app") +class TestRepoGroupsSettingsView(object): + + @pytest.mark.parametrize('repo_group_name', [ + 'gro', + u'12345', + ]) + def test_edit(self, user_util, autologin_user, repo_group_name): + user_util._test_name = repo_group_name + repo_group = user_util.create_repo_group() + + self.app.get( + route_path('edit_repo_group', repo_group_name=repo_group.group_name), + status=200) + + def test_update(self, csrf_token, autologin_user, user_util, rc_fixture): + repo_group = user_util.create_repo_group() + repo_group_name = repo_group.group_name + + description = 'description for newly created repo group' + form_data = rc_fixture._get_group_create_params( + group_name=repo_group.group_name, + group_description=description, + csrf_token=csrf_token, + repo_group_name=repo_group.group_name, + repo_group_owner=repo_group.user.username) + + response = self.app.post( + route_path('edit_repo_group', + repo_group_name=repo_group.group_name), + form_data, + status=302) + + assert_session_flash( + response, 'Repository Group `{}` updated successfully'.format( + repo_group_name)) + + def test_update_fails_when_parent_pointing_to_self( + self, csrf_token, user_util, autologin_user, rc_fixture): + group = user_util.create_repo_group() + response = self.app.post( + route_path('edit_repo_group', repo_group_name=group.group_name), + rc_fixture._get_group_create_params( + repo_group_name=group.group_name, + repo_group_owner=group.user.username, + repo_group=group.group_id, + csrf_token=csrf_token), + status=200 + ) + response.mustcontain( + '"{}" is not one of -1'.format( + group.group_id)) diff --git a/rhodecode/apps/repo_group/views/repo_group_advanced.py b/rhodecode/apps/repo_group/views/repo_group_advanced.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/views/repo_group_advanced.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2011-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging + +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound + +from rhodecode.apps._base import RepoGroupAppView +from rhodecode.lib import helpers as h +from rhodecode.lib import audit_logger +from rhodecode.lib.auth import ( + LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator) +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.meta import Session + +log = logging.getLogger(__name__) + + +class RepoGroupSettingsView(RepoGroupAppView): + def load_default_context(self): + c = self._get_local_tmpl_context() + self._register_global_c(c) + return c + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @view_config( + route_name='edit_repo_group_advanced', request_method='GET', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_repo_group_advanced(self): + c = self.load_default_context() + c.active = 'advanced' + c.repo_group = self.db_repo_group + return self._get_template_context(c) + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_group_advanced_delete', request_method='POST', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_repo_group_delete(self): + _ = self.request.translate + _ungettext = self.request.plularize + c = self.load_default_context() + c.repo_group = self.db_repo_group + + repos = c.repo_group.repositories.all() + if repos: + msg = _ungettext( + 'This repository group contains %(num)d repository and cannot be deleted', + 'This repository group contains %(num)d repositories and cannot be' + ' deleted', + len(repos)) % {'num': len(repos)} + h.flash(msg, category='warning') + raise HTTPFound( + h.route_path('edit_repo_group_advanced', + repo_group_name=self.db_repo_group_name)) + + children = c.repo_group.children.all() + if children: + msg = _ungettext( + 'This repository group contains %(num)d subgroup and cannot be deleted', + 'This repository group contains %(num)d subgroups and cannot be deleted', + len(children)) % {'num': len(children)} + h.flash(msg, category='warning') + raise HTTPFound( + h.route_path('edit_repo_group_advanced', + repo_group_name=self.db_repo_group_name)) + + try: + old_values = c.repo_group.get_api_data() + RepoGroupModel().delete(self.db_repo_group_name) + + audit_logger.store_web( + 'repo_group.delete', action_data={'old_data': old_values}, + user=c.rhodecode_user) + + Session().commit() + h.flash(_('Removed repository group `%s`') % self.db_repo_group_name, + category='success') + except Exception: + log.exception("Exception during deletion of repository group") + h.flash(_('Error occurred during deletion of repository group %s') + % self.db_repo_group_name, category='error') + + raise HTTPFound(h.route_path('repo_groups')) diff --git a/rhodecode/apps/repo_group/views/repo_group_permissions.py b/rhodecode/apps/repo_group/views/repo_group_permissions.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/views/repo_group_permissions.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2011-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging + +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound + +from rhodecode.apps._base import RepoGroupAppView +from rhodecode.lib import helpers as h +from rhodecode.lib import audit_logger +from rhodecode.lib.auth import ( + LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.forms import RepoGroupPermsForm +from rhodecode.model.meta import Session + +log = logging.getLogger(__name__) + + +class RepoGroupPermissionsView(RepoGroupAppView): + def load_default_context(self): + c = self._get_local_tmpl_context() + self._register_global_c(c) + return c + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @view_config( + route_name='edit_repo_group_perms', request_method='GET', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_repo_group_permissions(self): + c = self.load_default_context() + c.active = 'permissions' + c.repo_group = self.db_repo_group + return self._get_template_context(c) + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_group_perms_update', request_method='POST', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_repo_groups_permissions_update(self): + _ = self.request.translate + c = self.load_default_context() + c.active = 'perms' + c.repo_group = self.db_repo_group + + valid_recursive_choices = ['none', 'repos', 'groups', 'all'] + form = RepoGroupPermsForm(valid_recursive_choices)()\ + .to_python(self.request.POST) + + if not c.rhodecode_user.is_admin: + if self._revoke_perms_on_yourself(form): + msg = _('Cannot change permission for yourself as admin') + h.flash(msg, category='warning') + raise HTTPFound( + h.route_path('edit_repo_group_perms', + group_name=self.db_repo_group_name)) + + # iterate over all members(if in recursive mode) of this groups and + # set the permissions ! + # this can be potentially heavy operation + changes = RepoGroupModel().update_permissions( + c.repo_group, + form['perm_additions'], form['perm_updates'], form['perm_deletions'], + form['recursive']) + + action_data = { + 'added': changes['added'], + 'updated': changes['updated'], + 'deleted': changes['deleted'], + } + audit_logger.store_web( + 'repo_group.edit.permissions', action_data=action_data, + user=c.rhodecode_user) + + Session().commit() + h.flash(_('Repository Group permissions updated'), category='success') + raise HTTPFound( + h.route_path('edit_repo_group_perms', + repo_group_name=self.db_repo_group_name)) diff --git a/rhodecode/apps/repo_group/views/repo_group_settings.py b/rhodecode/apps/repo_group/views/repo_group_settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repo_group/views/repo_group_settings.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2011-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging +import deform + +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound + +from rhodecode.apps._base import RepoGroupAppView +from rhodecode.forms import RcForm +from rhodecode.lib import helpers as h +from rhodecode.lib import audit_logger +from rhodecode.lib.auth import ( + LoginRequired, HasPermissionAll, + HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.db import Session, RepoGroup +from rhodecode.model.scm import RepoGroupList +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.validation_schema.schemas import repo_group_schema + +log = logging.getLogger(__name__) + + +class RepoGroupSettingsView(RepoGroupAppView): + def load_default_context(self): + c = self._get_local_tmpl_context() + c.repo_group = self.db_repo_group + no_parrent = not c.repo_group.parent_group + can_create_in_root = self._can_create_repo_group() + + show_root_location = False + if no_parrent or can_create_in_root: + # we're global admin, we're ok and we can create TOP level groups + # or in case this group is already at top-level we also allow + # creation in root + show_root_location = True + + acl_groups = RepoGroupList( + RepoGroup.query().all(), + perm_set=['group.admin']) + c.repo_groups = RepoGroup.groups_choices( + groups=acl_groups, + show_empty_group=show_root_location) + # filter out current repo group + exclude_group_ids = [c.repo_group.group_id] + c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids, + c.repo_groups) + c.repo_groups_choices = map(lambda k: k[0], c.repo_groups) + + parent_group = c.repo_group.parent_group + + add_parent_group = (parent_group and ( + parent_group.group_id not in c.repo_groups_choices)) + if add_parent_group: + c.repo_groups_choices.append(parent_group.group_id) + c.repo_groups.append(RepoGroup._generate_choice(parent_group)) + + self._register_global_c(c) + return c + + def _can_create_repo_group(self, parent_group_id=None): + is_admin = HasPermissionAll('hg.admin')('group create controller') + create_repo_group = HasPermissionAll( + 'hg.repogroup.create.true')('group create controller') + if is_admin or (create_repo_group and not parent_group_id): + # we're global admin, or we have global repo group create + # permission + # we're ok and we can create TOP level groups + return True + elif parent_group_id: + # we check the permission if we can write to parent group + group = RepoGroup.get(parent_group_id) + group_name = group.group_name if group else None + if HasRepoGroupPermissionAny('group.admin')( + group_name, 'check if user is an admin of group'): + # we're an admin of passed in group, we're ok. + return True + else: + return False + return False + + def _get_schema(self, c, old_values=None): + return repo_group_schema.RepoGroupSettingsSchema().bind( + repo_group_repo_group_options=c.repo_groups_choices, + repo_group_repo_group_items=c.repo_groups, + + # user caller + user=self._rhodecode_user, + old_values=old_values + ) + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @view_config( + route_name='edit_repo_group', request_method='GET', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_settings(self): + c = self.load_default_context() + c.active = 'settings' + + defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name) + defaults['repo_group_owner'] = defaults['user'] + + schema = self._get_schema(c) + c.form = RcForm(schema, appstruct=defaults) + return self._get_template_context(c) + + @LoginRequired() + @HasRepoGroupPermissionAnyDecorator('group.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_group', request_method='POST', + renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako') + def edit_settings_update(self): + _ = self.request.translate + c = self.load_default_context() + c.active = 'settings' + + old_repo_group_name = self.db_repo_group_name + new_repo_group_name = old_repo_group_name + + old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name) + schema = self._get_schema(c, old_values=old_values) + + c.form = RcForm(schema) + pstruct = self.request.POST.items() + + try: + schema_data = c.form.validate(pstruct) + except deform.ValidationFailure as err_form: + return self._get_template_context(c) + + # data is now VALID, proceed with updates + # save validated data back into the updates dict + validated_updates = dict( + group_name=schema_data['repo_group']['repo_group_name_without_group'], + group_parent_id=schema_data['repo_group']['repo_group_id'], + user=schema_data['repo_group_owner'], + group_description=schema_data['repo_group_description'], + enable_locking=schema_data['repo_group_enable_locking'], + ) + + try: + RepoGroupModel().update(self.db_repo_group, validated_updates) + + audit_logger.store_web( + 'repo_group.edit', action_data={'old_data': old_values}, + user=c.rhodecode_user) + + Session().commit() + + # use the new full name for redirect once we know we updated + # the name on filesystem and in DB + new_repo_group_name = schema_data['repo_group_name'] + + h.flash(_('Repository Group `{}` updated successfully').format( + old_repo_group_name), category='success') + + except Exception: + log.exception("Exception during update or repository group") + h.flash(_('Error occurred during update of repository group %s') + % old_repo_group_name, category='error') + + raise HTTPFound( + h.route_path('edit_repo_group', repo_group_name=new_repo_group_name)) diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py --- a/rhodecode/apps/repository/__init__.py +++ b/rhodecode/apps/repository/__init__.py @@ -312,6 +312,7 @@ def includeme(config): config.add_route( name='edit_repo', pattern='/{repo_name:.*?[^/]}/settings', repo_route=True) + # update is POST on edit_repo # Settings advanced config.add_route( @@ -373,7 +374,6 @@ def includeme(config): name='edit_repo_remote_pull', pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True) - # Statistics config.add_route( name='edit_repo_statistics', diff --git a/rhodecode/apps/repository/tests/__init__.py b/rhodecode/apps/repository/tests/__init__.py --- a/rhodecode/apps/repository/tests/__init__.py +++ b/rhodecode/apps/repository/tests/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ diff --git a/rhodecode/apps/repository/tests/test_repo_settings.py b/rhodecode/apps/repository/tests/test_repo_settings.py --- a/rhodecode/apps/repository/tests/test_repo_settings.py +++ b/rhodecode/apps/repository/tests/test_repo_settings.py @@ -197,7 +197,7 @@ class TestAdminRepoSettings(object): assert_session_flash( response, - msg='Repository %s updated successfully' % (backend.repo_name)) + msg='Repository `%s` updated successfully' % (backend.repo_name)) repo = Repository.get_by_repo_name(backend.repo_name) assert repo.private is True @@ -218,7 +218,7 @@ class TestAdminRepoSettings(object): assert_session_flash( response, - msg='Repository %s updated successfully' % (backend.repo_name)) + msg='Repository `%s` updated successfully' % (backend.repo_name)) assert backend.repo.private is False # we turn off private now the repo default permission should stay None diff --git a/rhodecode/apps/repository/views/repo_settings.py b/rhodecode/apps/repository/views/repo_settings.py --- a/rhodecode/apps/repository/views/repo_settings.py +++ b/rhodecode/apps/repository/views/repo_settings.py @@ -164,7 +164,7 @@ class RepoSettingsView(RepoAppView): Session().commit() - h.flash(_('Repository {} updated successfully').format( + h.flash(_('Repository `{}` updated successfully').format( old_repo_name), category='success') except Exception: log.exception("Exception during update of repository") diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -172,53 +172,6 @@ def make_map(config): # CUSTOM ROUTES HERE #========================================================================== - # ADMIN REPOSITORY GROUPS ROUTES - with rmap.submapper(path_prefix=ADMIN_PREFIX, - controller='admin/repo_groups') as m: - m.connect('repo_groups', '/repo_groups', - action='create', conditions={'method': ['POST']}) - m.connect('repo_groups', '/repo_groups', - action='index', conditions={'method': ['GET']}) - m.connect('new_repo_group', '/repo_groups/new', - action='new', conditions={'method': ['GET']}) - m.connect('update_repo_group', '/repo_groups/{group_name}', - action='update', conditions={'method': ['PUT'], - 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - - # EXTRAS REPO GROUP ROUTES - m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', - action='edit', - conditions={'method': ['GET'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', - action='edit', - conditions={'method': ['PUT'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - - m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', - action='edit_repo_group_advanced', - conditions={'method': ['GET'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', - action='edit_repo_group_advanced', - conditions={'method': ['PUT'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - - m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', - action='edit_repo_group_perms', - conditions={'method': ['GET'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', - action='update_perms', - conditions={'method': ['PUT'], 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - - m.connect('delete_repo_group', '/repo_groups/{group_name}', - action='delete', conditions={'method': ['DELETE'], - 'function': check_group}, - requirements=URL_NAME_REQUIREMENTS) - # ADMIN SETTINGS ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/settings') as m: diff --git a/rhodecode/controllers/admin/repo_groups.py b/rhodecode/controllers/admin/repo_groups.py deleted file mode 100644 --- a/rhodecode/controllers/admin/repo_groups.py +++ /dev/null @@ -1,405 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2017 RhodeCode GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License, version 3 -# (only), as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# This program is dual-licensed. If you wish to learn more about the -# RhodeCode Enterprise Edition, including its added features, Support services, -# and proprietary license terms, please see https://rhodecode.com/licenses/ - - -""" -Repository groups controller for RhodeCode -""" - -import logging -import formencode - -from formencode import htmlfill - -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import abort, redirect -from pylons.i18n.translation import _, ungettext - -from rhodecode.lib import auth -from rhodecode.lib import helpers as h -from rhodecode.lib import audit_logger -from rhodecode.lib.ext_json import json -from rhodecode.lib.auth import ( - LoginRequired, NotAnonymous, HasPermissionAll, - HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator) -from rhodecode.lib.base import BaseController, render -from rhodecode.lib.utils2 import safe_int -from rhodecode.model.db import RepoGroup, User -from rhodecode.model.scm import RepoGroupList -from rhodecode.model.repo_group import RepoGroupModel -from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm -from rhodecode.model.meta import Session - - -log = logging.getLogger(__name__) - - -class RepoGroupsController(BaseController): - """REST Controller styled on the Atom Publishing Protocol""" - - @LoginRequired() - def __before__(self): - super(RepoGroupsController, self).__before__() - - def __load_defaults(self, allow_empty_group=False, repo_group=None): - if self._can_create_repo_group(): - # we're global admin, we're ok and we can create TOP level groups - allow_empty_group = True - - # override the choices for this form, we need to filter choices - # and display only those we have ADMIN right - groups_with_admin_rights = RepoGroupList( - RepoGroup.query().all(), - perm_set=['group.admin']) - c.repo_groups = RepoGroup.groups_choices( - groups=groups_with_admin_rights, - show_empty_group=allow_empty_group) - - if repo_group: - # exclude filtered ids - exclude_group_ids = [repo_group.group_id] - c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids, - c.repo_groups) - c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) - parent_group = repo_group.parent_group - - add_parent_group = (parent_group and ( - unicode(parent_group.group_id) not in c.repo_groups_choices)) - if add_parent_group: - c.repo_groups_choices.append(unicode(parent_group.group_id)) - c.repo_groups.append(RepoGroup._generate_choice(parent_group)) - - def __load_data(self, group_id): - """ - Load defaults settings for edit, and update - - :param group_id: - """ - repo_group = RepoGroup.get_or_404(group_id) - data = repo_group.get_dict() - data['group_name'] = repo_group.name - - # fill owner - if repo_group.user: - data.update({'user': repo_group.user.username}) - else: - replacement_user = User.get_first_super_admin().username - data.update({'user': replacement_user}) - - # fill repository group users - for p in repo_group.repo_group_to_perm: - data.update({ - 'u_perm_%s' % p.user.user_id: p.permission.permission_name}) - - # fill repository group user groups - for p in repo_group.users_group_to_perm: - data.update({ - 'g_perm_%s' % p.users_group.users_group_id: - p.permission.permission_name}) - # html and form expects -1 as empty parent group - data['group_parent_id'] = data['group_parent_id'] or -1 - return data - - def _revoke_perms_on_yourself(self, form_result): - _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]), - form_result['perm_updates']) - _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]), - form_result['perm_additions']) - _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]), - form_result['perm_deletions']) - admin_perm = 'group.admin' - if _updates and _updates[0][1] != admin_perm or \ - _additions and _additions[0][1] != admin_perm or \ - _deletions and _deletions[0][1] != admin_perm: - return True - return False - - def _can_create_repo_group(self, parent_group_id=None): - is_admin = HasPermissionAll('hg.admin')('group create controller') - create_repo_group = HasPermissionAll( - 'hg.repogroup.create.true')('group create controller') - if is_admin or (create_repo_group and not parent_group_id): - # we're global admin, or we have global repo group create - # permission - # we're ok and we can create TOP level groups - return True - elif parent_group_id: - # we check the permission if we can write to parent group - group = RepoGroup.get(parent_group_id) - group_name = group.group_name if group else None - if HasRepoGroupPermissionAll('group.admin')( - group_name, 'check if user is an admin of group'): - # we're an admin of passed in group, we're ok. - return True - else: - return False - return False - - @NotAnonymous() - def index(self): - repo_group_list = RepoGroup.get_all_repo_groups() - _perms = ['group.admin'] - repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms) - repo_group_data = RepoGroupModel().get_repo_groups_as_dict( - repo_group_list=repo_group_list_acl, admin=True) - c.data = json.dumps(repo_group_data) - return render('admin/repo_groups/repo_groups.mako') - - # perm checks inside - @NotAnonymous() - @auth.CSRFRequired() - def create(self): - - parent_group_id = safe_int(request.POST.get('group_parent_id')) - can_create = self._can_create_repo_group(parent_group_id) - - self.__load_defaults() - # permissions for can create group based on parent_id are checked - # here in the Form - available_groups = map(lambda k: unicode(k[0]), c.repo_groups) - repo_group_form = RepoGroupForm(available_groups=available_groups, - can_create_in_root=can_create)() - try: - owner = c.rhodecode_user - form_result = repo_group_form.to_python(dict(request.POST)) - repo_group = RepoGroupModel().create( - group_name=form_result['group_name_full'], - group_description=form_result['group_description'], - owner=owner.user_id, - copy_permissions=form_result['group_copy_permissions'] - ) - Session().flush() - - repo_group_data = repo_group.get_api_data() - audit_logger.store_web( - 'repo_group.create', action_data={'data': repo_group_data}, - user=c.rhodecode_user) - - Session().commit() - - _new_group_name = form_result['group_name_full'] - - repo_group_url = h.link_to( - _new_group_name, - h.route_path('repo_group_home', repo_group_name=_new_group_name)) - h.flash(h.literal(_('Created repository group %s') - % repo_group_url), category='success') - - except formencode.Invalid as errors: - return htmlfill.render( - render('admin/repo_groups/repo_group_add.mako'), - defaults=errors.value, - errors=errors.error_dict or {}, - prefix_error=False, - encoding="UTF-8", - force_defaults=False) - except Exception: - log.exception("Exception during creation of repository group") - h.flash(_('Error occurred during creation of repository group %s') - % request.POST.get('group_name'), category='error') - - # TODO: maybe we should get back to the main view, not the admin one - return redirect(url('repo_groups', parent_group=parent_group_id)) - - # perm checks inside - @NotAnonymous() - def new(self): - # perm check for admin, create_group perm or admin of parent_group - parent_group_id = safe_int(request.GET.get('parent_group')) - if not self._can_create_repo_group(parent_group_id): - return abort(403) - - self.__load_defaults() - return render('admin/repo_groups/repo_group_add.mako') - - @HasRepoGroupPermissionAnyDecorator('group.admin') - @auth.CSRFRequired() - def update(self, group_name): - - c.repo_group = RepoGroupModel()._get_repo_group(group_name) - can_create_in_root = self._can_create_repo_group() - show_root_location = can_create_in_root - if not c.repo_group.parent_group: - # this group don't have a parrent so we should show empty value - show_root_location = True - self.__load_defaults(allow_empty_group=show_root_location, - repo_group=c.repo_group) - - repo_group_form = RepoGroupForm( - edit=True, old_data=c.repo_group.get_dict(), - available_groups=c.repo_groups_choices, - can_create_in_root=can_create_in_root, allow_disabled=True)() - - old_values = c.repo_group.get_api_data() - try: - form_result = repo_group_form.to_python(dict(request.POST)) - gr_name = form_result['group_name'] - new_gr = RepoGroupModel().update(group_name, form_result) - - audit_logger.store_web( - 'repo_group.edit', action_data={'old_data': old_values}, - user=c.rhodecode_user) - - Session().commit() - h.flash(_('Updated repository group %s') % (gr_name,), - category='success') - # we now have new name ! - group_name = new_gr.group_name - except formencode.Invalid as errors: - c.active = 'settings' - return htmlfill.render( - render('admin/repo_groups/repo_group_edit.mako'), - defaults=errors.value, - errors=errors.error_dict or {}, - prefix_error=False, - encoding="UTF-8", - force_defaults=False) - except Exception: - log.exception("Exception during update or repository group") - h.flash(_('Error occurred during update of repository group %s') - % request.POST.get('group_name'), category='error') - - return redirect(url('edit_repo_group', group_name=group_name)) - - @HasRepoGroupPermissionAnyDecorator('group.admin') - @auth.CSRFRequired() - def delete(self, group_name): - gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name) - repos = gr.repositories.all() - if repos: - msg = ungettext( - 'This group contains %(num)d repository and cannot be deleted', - 'This group contains %(num)d repositories and cannot be' - ' deleted', - len(repos)) % {'num': len(repos)} - h.flash(msg, category='warning') - return redirect(url('repo_groups')) - - children = gr.children.all() - if children: - msg = ungettext( - 'This group contains %(num)d subgroup and cannot be deleted', - 'This group contains %(num)d subgroups and cannot be deleted', - len(children)) % {'num': len(children)} - h.flash(msg, category='warning') - return redirect(url('repo_groups')) - - try: - old_values = gr.get_api_data() - RepoGroupModel().delete(group_name) - - audit_logger.store_web( - 'repo_group.delete', action_data={'old_data': old_values}, - user=c.rhodecode_user) - - Session().commit() - h.flash(_('Removed repository group %s') % group_name, - category='success') - except Exception: - log.exception("Exception during deletion of repository group") - h.flash(_('Error occurred during deletion of repository group %s') - % group_name, category='error') - - return redirect(url('repo_groups')) - - @HasRepoGroupPermissionAnyDecorator('group.admin') - def edit(self, group_name): - - c.active = 'settings' - - c.repo_group = RepoGroupModel()._get_repo_group(group_name) - # we can only allow moving empty group if it's already a top-level - # group, ie has no parents, or we're admin - can_create_in_root = self._can_create_repo_group() - show_root_location = can_create_in_root - if not c.repo_group.parent_group: - # this group don't have a parrent so we should show empty value - show_root_location = True - self.__load_defaults(allow_empty_group=show_root_location, - repo_group=c.repo_group) - defaults = self.__load_data(c.repo_group.group_id) - - return htmlfill.render( - render('admin/repo_groups/repo_group_edit.mako'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) - - @HasRepoGroupPermissionAnyDecorator('group.admin') - def edit_repo_group_advanced(self, group_name): - c.active = 'advanced' - c.repo_group = RepoGroupModel()._get_repo_group(group_name) - - return render('admin/repo_groups/repo_group_edit.mako') - - @HasRepoGroupPermissionAnyDecorator('group.admin') - def edit_repo_group_perms(self, group_name): - c.active = 'perms' - c.repo_group = RepoGroupModel()._get_repo_group(group_name) - self.__load_defaults() - defaults = self.__load_data(c.repo_group.group_id) - - return htmlfill.render( - render('admin/repo_groups/repo_group_edit.mako'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) - - @HasRepoGroupPermissionAnyDecorator('group.admin') - @auth.CSRFRequired() - def update_perms(self, group_name): - """ - Update permissions for given repository group - """ - - c.repo_group = RepoGroupModel()._get_repo_group(group_name) - valid_recursive_choices = ['none', 'repos', 'groups', 'all'] - form = RepoGroupPermsForm(valid_recursive_choices)().to_python( - request.POST) - - if not c.rhodecode_user.is_admin: - if self._revoke_perms_on_yourself(form): - msg = _('Cannot change permission for yourself as admin') - h.flash(msg, category='warning') - return redirect( - url('edit_repo_group_perms', group_name=group_name)) - - # iterate over all members(if in recursive mode) of this groups and - # set the permissions ! - # this can be potentially heavy operation - changes = RepoGroupModel().update_permissions( - c.repo_group, - form['perm_additions'], form['perm_updates'], form['perm_deletions'], - form['recursive']) - - action_data = { - 'added': changes['added'], - 'updated': changes['updated'], - 'deleted': changes['deleted'], - } - audit_logger.store_web( - 'repo_group.edit.permissions', action_data=action_data, - user=c.rhodecode_user) - - Session().commit() - h.flash(_('Repository Group permissions updated'), category='success') - return redirect(url('edit_repo_group_perms', group_name=group_name)) diff --git a/rhodecode/integrations/routes.py b/rhodecode/integrations/routes.py --- a/rhodecode/integrations/routes.py +++ b/rhodecode/integrations/routes.py @@ -71,7 +71,7 @@ def includeme(config): # repo group integrations config.add_route('repo_group_integrations_home', - add_route_requirements('/{repo_group_name}/settings/integrations'), + add_route_requirements('/{repo_group_name}/_settings/integrations'), repo_group_route=True) config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView', @@ -81,7 +81,7 @@ def includeme(config): route_name='repo_group_integrations_home') config.add_route('repo_group_integrations_new', - add_route_requirements('/{repo_group_name}/settings/integrations/new'), + add_route_requirements('/{repo_group_name}/_settings/integrations/new'), repo_group_route=True) config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView', attr='new_integration', @@ -90,7 +90,7 @@ def includeme(config): route_name='repo_group_integrations_new') config.add_route('repo_group_integrations_list', - add_route_requirements('/{repo_group_name}/settings/integrations/{integration}'), + add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}'), repo_group_route=True, custom_predicates=(valid_integration,)) config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView', @@ -100,7 +100,7 @@ def includeme(config): route_name='repo_group_integrations_list') config.add_route('repo_group_integrations_create', - add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/new'), + add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/new'), repo_group_route=True, custom_predicates=(valid_integration,)) config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView', @@ -115,7 +115,7 @@ def includeme(config): route_name='repo_group_integrations_create') config.add_route('repo_group_integrations_edit', - add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/{integration_id}'), + add_route_requirements('/{repo_group_name}/_settings/integrations/{integration}/{integration_id}'), repo_group_route=True, custom_predicates=(valid_integration,)) diff --git a/rhodecode/integrations/tests/test_integrations.py b/rhodecode/integrations/tests/test_integrations.py --- a/rhodecode/integrations/tests/test_integrations.py +++ b/rhodecode/integrations/tests/test_integrations.py @@ -145,7 +145,7 @@ class TestRepoIntegrationsView(TestInteg class TestRepoGroupIntegrationsView(TestIntegrationsView): def test_index_no_integrations(self, test_repo_group): - url = '/{repo_group_name}/settings/integrations'.format( + url = '/{repo_group_name}/_settings/integrations'.format( repo_group_name=test_repo_group.group_name) response = self.app.get(url) @@ -155,7 +155,7 @@ class TestRepoGroupIntegrationsView(Test def test_index_with_integrations( self, test_repo_group, repogroup_integration_stub): - url = '/{repo_group_name}/settings/integrations'.format( + url = '/{repo_group_name}/_settings/integrations'.format( repo_group_name=test_repo_group.group_name) stub_name = repogroup_integration_stub.name @@ -167,7 +167,7 @@ class TestRepoGroupIntegrationsView(Test def test_new_integration_page(self, test_repo_group): repo_group_name = test_repo_group.group_name - url = '/{repo_group_name}/settings/integrations/new'.format( + url = '/{repo_group_name}/_settings/integrations/new'.format( repo_group_name=test_repo_group.group_name) response = self.app.get(url) @@ -177,7 +177,7 @@ class TestRepoGroupIntegrationsView(Test for integration_key, integration_obj in integration_type_registry.items(): if not integration_obj.is_dummy: nurl = ( - '/{repo_group_name}/settings/integrations/{integration}/new').format( + '/{repo_group_name}/_settings/integrations/{integration}/new').format( repo_group_name=repo_group_name, integration=integration_key) response.mustcontain(nurl) @@ -188,7 +188,7 @@ class TestRepoGroupIntegrationsView(Test self, test_repo_group, IntegrationType): repo_group_name = test_repo_group.group_name - url = ('/{repo_group_name}/settings/integrations/{integration_key}/new' + url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new' ).format(repo_group_name=repo_group_name, integration_key=IntegrationType.key) @@ -202,7 +202,7 @@ class TestRepoGroupIntegrationsView(Test StubIntegrationType, csrf_token): repo_group_name = test_repo_group.group_name - url = ('/{repo_group_name}/settings/integrations/{integration_key}/new' + url = ('/{repo_group_name}/_settings/integrations/{integration_key}/new' ).format(repo_group_name=repo_group_name, integration_key=StubIntegrationType.key) @@ -232,9 +232,9 @@ def _post_integration_test_helper(app, u ('repo:%s' % repo_name, '/%s/settings/integrations' % repo_name), ('repogroup:%s' % repo_group_name, - '/%s/settings/integrations' % repo_group_name), + '/%s/_settings/integrations' % repo_group_name), ('repogroup-recursive:%s' % repo_group_name, - '/%s/settings/integrations' % repo_group_name), + '/%s/_settings/integrations' % repo_group_name), ] for scope, destination in scopes_destinations: diff --git a/rhodecode/model/repo_group.py b/rhodecode/model/repo_group.py --- a/rhodecode/model/repo_group.py +++ b/rhodecode/model/repo_group.py @@ -254,8 +254,11 @@ class RepoGroupModel(BaseModel): # functions can delete this cleanup_group = self.check_exist_filesystem(group_name, exc_on_failure=False) + user = self._get_user(owner) + if not user: + raise ValueError('Owner %s not found as rhodecode user', owner) + try: - user = self._get_user(owner) new_repo_group = RepoGroup() new_repo_group.user = user new_repo_group.group_description = group_description or group_name @@ -736,3 +739,26 @@ class RepoGroupModel(BaseModel): repo_group_data.append(row) return repo_group_data + + def _get_defaults(self, repo_group_name): + repo_group = RepoGroup.get_by_group_name(repo_group_name) + + if repo_group is None: + return None + + defaults = repo_group.get_dict() + defaults['repo_group_name'] = repo_group.name + defaults['repo_group_description'] = repo_group.group_description + defaults['repo_group_enable_locking'] = repo_group.enable_locking + + # we use -1 as this is how in HTML, we mark an empty group + defaults['repo_group'] = defaults['group_parent_id'] or -1 + + # fill owner + if repo_group.user: + defaults.update({'user': repo_group.user.username}) + else: + replacement_user = User.get_first_super_admin().username + defaults.update({'user': replacement_user}) + + return defaults diff --git a/rhodecode/model/validation_schema/schemas/repo_group_schema.py b/rhodecode/model/validation_schema/schemas/repo_group_schema.py --- a/rhodecode/model/validation_schema/schemas/repo_group_schema.py +++ b/rhodecode/model/validation_schema/schemas/repo_group_schema.py @@ -20,6 +20,7 @@ import colander +import deform.widget from rhodecode.translation import _ from rhodecode.model.validation_schema import validators, preparers, types @@ -143,6 +144,19 @@ def deferred_repo_group_name_validator(n return validators.valid_name_validator +@colander.deferred +def deferred_repo_group_validator(node, kw): + options = kw.get( + 'repo_group_repo_group_options') + return colander.OneOf([x for x in options]) + + +@colander.deferred +def deferred_repo_group_widget(node, kw): + items = kw.get('repo_group_repo_group_items') + return deform.widget.Select2Widget(values=items) + + class GroupType(colander.Mapping): def _validate(self, node, value): try: @@ -208,15 +222,15 @@ class RepoGroupSchema(colander.Schema): validator=deferred_repo_group_owner_validator) repo_group_description = colander.SchemaNode( - colander.String(), missing='') + colander.String(), missing='', widget=deform.widget.TextAreaWidget()) repo_group_copy_permissions = colander.SchemaNode( types.StringBooleanType(), - missing=False) + missing=False, widget=deform.widget.CheckboxWidget()) repo_group_enable_locking = colander.SchemaNode( types.StringBooleanType(), - missing=False) + missing=False, widget=deform.widget.CheckboxWidget()) def deserialize(self, cstruct): """ @@ -238,3 +252,33 @@ class RepoGroupSchema(colander.Schema): third.deserialize({'unique_repo_group_name': validated_name}) return appstruct + + +class RepoGroupSettingsSchema(RepoGroupSchema): + repo_group = colander.SchemaNode( + colander.Integer(), + validator=deferred_repo_group_validator, + widget=deferred_repo_group_widget, + missing='') + + def deserialize(self, cstruct): + """ + Custom deserialize that allows to chain validation, and verify + permissions, and as last step uniqueness + """ + + # first pass, to validate given data + appstruct = super(RepoGroupSchema, self).deserialize(cstruct) + validated_name = appstruct['repo_group_name'] + + # second pass to validate permissions to repo_group + second = RepoGroupAccessSchema().bind(**self.bindings) + appstruct_second = second.deserialize({'repo_group': validated_name}) + # save result + appstruct['repo_group'] = appstruct_second['repo_group'] + + # thirds to validate uniqueness + third = RepoGroupNameUniqueSchema().bind(**self.bindings) + third.deserialize({'unique_repo_group_name': validated_name}) + + return appstruct diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -20,11 +20,11 @@ function registerRCRoutes() { pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']); pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']); - pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']); - pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']); - pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']); - pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); - pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']); + pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']); + pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']); + pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']); + pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); + pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']); pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']); pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']); pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); @@ -100,6 +100,9 @@ function registerRCRoutes() { pyroutes.register('repos', '/_admin/repos', []); pyroutes.register('repo_new', '/_admin/repos/new', []); pyroutes.register('repo_create', '/_admin/repos/create', []); + pyroutes.register('repo_groups', '/_admin/repo_groups', []); + pyroutes.register('repo_group_new', '/_admin/repo_group/new', []); + pyroutes.register('repo_group_create', '/_admin/repo_group/create', []); pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); pyroutes.register('channelstream_proxy', '/_channelstream', []); @@ -223,6 +226,11 @@ function registerRCRoutes() { pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); + pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']); + pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']); + pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']); + pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']); + pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']); pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']); diff --git a/rhodecode/templates/admin/integrations/form.mako b/rhodecode/templates/admin/integrations/form.mako --- a/rhodecode/templates/admin/integrations/form.mako +++ b/rhodecode/templates/admin/integrations/form.mako @@ -14,9 +14,9 @@ %elif c.repo_group: ${h.link_to(_('Admin'),h.route_path('admin_home'))} » - ${h.link_to(_('Repository Groups'),h.url('repo_groups'))} + ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))} » - ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))} + ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))} » ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))} » diff --git a/rhodecode/templates/admin/integrations/list.mako b/rhodecode/templates/admin/integrations/list.mako --- a/rhodecode/templates/admin/integrations/list.mako +++ b/rhodecode/templates/admin/integrations/list.mako @@ -7,9 +7,9 @@ %elif c.repo_group: ${h.link_to(_('Admin'),h.route_path('admin_home'))} » - ${h.link_to(_('Repository Groups'),h.url('repo_groups'))} + ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))} » - ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))} + ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))} %else: ${h.link_to(_('Admin'),h.route_path('admin_home'))} » diff --git a/rhodecode/templates/admin/integrations/new.mako b/rhodecode/templates/admin/integrations/new.mako --- a/rhodecode/templates/admin/integrations/new.mako +++ b/rhodecode/templates/admin/integrations/new.mako @@ -10,9 +10,9 @@ %elif c.repo_group: ${h.link_to(_('Admin'),h.route_path('admin_home'))} » - ${h.link_to(_('Repository Groups'),h.url('repo_groups'))} + ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))} » - ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))} + ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))} » ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))} %else: diff --git a/rhodecode/templates/admin/repo_groups/repo_group_add.mako b/rhodecode/templates/admin/repo_groups/repo_group_add.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_add.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_add.mako @@ -11,7 +11,7 @@ <%def name="breadcrumbs_links()"> ${h.link_to(_('Admin'),h.route_path('admin_home'))} » - ${h.link_to(_('Repository groups'),h.url('repo_groups'))} + ${h.link_to(_('Repository groups'),h.route_path('repo_groups'))} » ${_('Add Repository Group')} @@ -27,7 +27,7 @@ ${self.breadcrumbs()} - ${h.secure_form(h.url('repo_groups'), request=request)} + ${h.secure_form(h.route_path('repo_group_create'), request=request)}
diff --git a/rhodecode/templates/admin/repo_groups/repo_group_edit.mako b/rhodecode/templates/admin/repo_groups/repo_group_edit.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_edit.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit.mako @@ -11,7 +11,7 @@ <%def name="breadcrumbs_links()"> ${h.link_to(_('Admin'),h.route_path('admin_home'))} » - ${h.link_to(_('Repository Groups'),h.url('repo_groups'))} + ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))} %if c.repo_group.parent_group: » ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))} %endif @@ -21,7 +21,7 @@ <%def name="breadcrumbs_side_links()"> @@ -45,9 +45,9 @@ ##main diff --git a/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako b/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako @@ -28,7 +28,7 @@

${_('Delete repository group')}

- ${h.secure_form(h.url('delete_repo_group', group_name=c.repo_group.group_name),method='delete', request=request)} + ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=c.repo_group.group_name), request=request)} diff --git a/rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako b/rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako rename from rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako rename to rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako @@ -5,14 +5,14 @@

${_('Repository Group Permissions')}

- ${h.secure_form(h.url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put', request=request)} + ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
- + ## USERS @@ -25,7 +25,6 @@ %else: - ##forbid revoking permission from yourself, except if you're an super admin + ##forbid revoking permission from yourself, except if you're an super admin %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin: - - - - + + + + - - - - + + + +
${_('None')} ${_('Read')} ${_('Write')} ${_('Admin')}${_('User/User Group')}${_('User/User Group')}
${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")} ${base.gravatar(_user.email, 16)} - ${h.link_to_user(_user.username)} %if getattr(_user, 'admin_row', None): (${_('super admin')}) @@ -33,18 +32,17 @@ %if getattr(_user, 'owner_row', None): (${_('owner')}) %endif -
${h.radio('u_perm_%s' % _user.user_id,'group.none')}${h.radio('u_perm_%s' % _user.user_id,'group.read')}${h.radio('u_perm_%s' % _user.user_id,'group.write')}${h.radio('u_perm_%s' % _user.user_id,'group.admin')}${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')} ${base.gravatar(_user.email, 16)} @@ -89,10 +87,10 @@ ## USER GROUPS %for _user_group in c.repo_group.permission_user_groups():
${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')} %if h.HasPermissionAny('hg.admin')(): diff --git a/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako --- a/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako @@ -6,7 +6,7 @@

${_('Settings for Repository Group: %s') % c.repo_group.name}

- ${h.secure_form(h.url('update_repo_group',group_name=c.repo_group.group_name),method='put', request=request)} + ${h.secure_form(h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name), request=request)}
@@ -15,13 +15,26 @@
- ${h.text('group_name',class_='medium')} + ${c.form['repo_group_name'].render(css_class='medium', oid='group_name')|n} + ${c.form.render_error(request, c.form['repo_group_name'])|n} +
+
+ +
+
+ +
+
+ ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n} + ${c.form.render_error(request, c.form['repo_group'])|n} + +

${_('Optional select a parent group to move this repository group into.')}

- +
@@ -29,20 +42,22 @@ ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
- ${h.text('user', class_="medium", autocomplete="off")} + ${c.form['repo_group_owner'].render(css_class='medium', oid='repo_group_owner')|n}
- + ${c.form.render_error(request, c.form['repo_group_owner'])|n}

${_('Change owner of this repository group.')}

- +
- ${h.textarea('group_description',cols=23,rows=5,class_="medium")} + ${c.form['repo_group_description'].render(css_class='medium', oid='repo_group_description')|n} + ${c.form.render_error(request, c.form['repo_group_description'])|n} + <% metatags_url = h.literal('''meta-tags''') %> ${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}
-
- -
-
- ${h.select('group_parent_id','',c.repo_groups,class_="medium")} -
-
-
- +
- ${h.checkbox('enable_locking',value="True")} + ${c.form['repo_group_enable_locking'].render(css_class='medium', oid='repo_group_enable_locking')|n} + ${c.form.render_error(request, c.form['repo_group_enable_locking'])|n} + ${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}
+
${h.submit('save',_('Save'),class_="btn")} ${h.reset('reset',_('Reset'),class_="btn")} @@ -80,11 +90,6 @@
diff --git a/rhodecode/templates/admin/repo_groups/repo_groups.mako b/rhodecode/templates/admin/repo_groups/repo_groups.mako --- a/rhodecode/templates/admin/repo_groups/repo_groups.mako +++ b/rhodecode/templates/admin/repo_groups/repo_groups.mako @@ -24,7 +24,7 @@ diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako --- a/rhodecode/templates/base/base.mako +++ b/rhodecode/templates/base/base.mako @@ -74,7 +74,7 @@