repo-groups: moved to pyramid

This commit is contained in:
Marcin Kuzminski 2017-10-17 14:06:51 +02:00
parent d702ba605f
commit 69a865a7a3
41 changed files with 1178 additions and 756 deletions

View file

@ -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):

View file

@ -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()

View file

@ -19,107 +19,85 @@
# 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 (
url, TestController, assert_session_flash, GIT_REPO, HG_REPO,
TESTS_TMP_PATH, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
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
def test_update(app, csrf_token, autologin_user, user_util):
repo_group = user_util.create_repo_group()
description = 'description for newly created repo group'
Session().commit()
response = app.post(
url('update_repo_group', group_name=repo_group.group_name),
fixture._get_group_create_params(
group_name=repo_group.group_name,
group_description=description,
csrf_token=csrf_token,
_method='PUT')
)
# TODO: anderson: johbo: we believe that this update should return
# a redirect instead of rendering the template.
assert response.status_code == 200
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 test_edit(app, user_util, autologin_user):
repo_group = user_util.create_repo_group()
Session().commit()
response = app.get(
url('edit_repo_group', group_name=repo_group.group_name))
assert response.status_code == 200
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
def test_edit_repo_group_perms(app, user_util, autologin_user):
repo_group = user_util.create_repo_group()
Session().commit()
response = app.get(
url('edit_repo_group_perms', group_name=repo_group.group_name))
assert response.status_code == 200
def test_update_fails_when_parent_pointing_to_self(
app, csrf_token, user_util, autologin_user):
group = user_util.create_repo_group()
response = app.post(
url('update_repo_group', group_name=group.group_name),
fixture._get_group_create_params(
group_parent_id=group.group_id,
csrf_token=csrf_token,
_method='PUT')
)
response.mustcontain(
'<select class="medium error" id="group_parent_id"'
' name="group_parent_id">')
response.mustcontain('<span class="error-message">Value must be one of:')
class _BaseTest(TestController):
REPO_GROUP = None
NEW_REPO_GROUP = None
REPO = None
REPO_TYPE = None
def test_index(self):
self.log_user()
response = self.app.get(url('repo_groups'))
@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_index_after_creating_group(self):
self.log_user()
def test_show_repo_groups_after_creating_group(self, autologin_user):
fixture.create_repo_group('test_repo_group')
response = self.app.get(url('repo_groups'))
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):
self.log_user()
self.app.get(url('new_repo_group'))
def test_new(self, autologin_user):
self.app.get(route_path('repo_group_new'))
def test_new_by_regular_user_no_permission(self):
self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
self.app.get(url('new_repo_group'), status=403)
def test_new_with_parent_group(self, autologin_user, user_util):
gr = user_util.create_repo_group()
def test_create(self):
self.log_user()
repo_group_name = self.NEW_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(
url('repo_groups'),
route_path('repo_group_create'),
fixture._get_group_create_params(
group_name=repo_group_name,
group_description=description,
csrf_token=self.csrf_token))
csrf_token=csrf_token))
# run the check page that triggers the flash message
repo_gr_url = h.route_path(
@ -151,9 +129,14 @@ class _BaseTest(TestController):
RepoGroupModel().delete(repo_group_name_unicode)
Session().commit()
def test_create_subgroup(self, user_util):
self.log_user()
repo_group_name = self.NEW_REPO_GROUP
@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
@ -163,65 +146,31 @@ class _BaseTest(TestController):
try:
response = self.app.post(
url('repo_groups'),
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=self.csrf_token))
csrf_token=csrf_token))
assert_session_flash(
response,
u'Created repository group <a href="%s">%s</a>' % (
h.route_path('repo_group_home', repo_group_name=expected_group_name),
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, user_util):
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.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
self.app.get(
url('new_repo_group', parent_group=parent_group_id,),
route_path('repo_group_new',
params=dict(parent_group=parent_group_id), ),
status=403)
class TestRepoGroupsControllerGIT(_BaseTest):
REPO_GROUP = None
NEW_REPO_GROUP = 'git_repo'
REPO = GIT_REPO
REPO_TYPE = 'git'
class TestRepoGroupsControllerNonAsciiGit(_BaseTest):
REPO_GROUP = None
NEW_REPO_GROUP = 'git_repo_ąć'
REPO = GIT_REPO
REPO_TYPE = 'git'
class TestRepoGroupsControllerHG(_BaseTest):
REPO_GROUP = None
NEW_REPO_GROUP = 'hg_repo'
REPO = HG_REPO
REPO_TYPE = 'hg'
class TestRepoGroupsControllerNumericalHG(_BaseTest):
REPO_GROUP = None
NEW_REPO_GROUP = '12345'
REPO = HG_REPO
REPO_TYPE = 'hg'
class TestRepoGroupsControllerNonAsciiHG(_BaseTest):
REPO_GROUP = None
NEW_REPO_GROUP = 'hg_repo_ąć'
REPO = HG_REPO
REPO_TYPE = 'hg'

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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']))

View file

@ -22,7 +22,36 @@ from rhodecode.apps._base import add_route_with_slash
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')

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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/

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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')

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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(
'<span class="error-message">"{}" is not one of -1'.format(
group.group_id))

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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'))

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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))

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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))

View file

@ -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',

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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/

View file

@ -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

View file

@ -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")

View file

@ -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:

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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))

View file

@ -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,))

View file

@ -145,7 +145,7 @@ class TestRepoIntegrationsView(TestIntegrationsView):
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(TestIntegrationsView):
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(TestIntegrationsView):
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(TestIntegrationsView):
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(TestIntegrationsView):
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(TestIntegrationsView):
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, url, csrf_token, repo, repo_group,
('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:

View file

@ -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

View file

@ -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(node, kw):
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

View file

@ -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']);

View file

@ -14,9 +14,9 @@
%elif c.repo_group:
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
&raquo;
${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))}
&raquo;
${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
&raquo;

View file

@ -7,9 +7,9 @@
%elif c.repo_group:
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
&raquo;
${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'))}
&raquo;

View file

@ -10,9 +10,9 @@
%elif c.repo_group:
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
&raquo;
${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))}
&raquo;
${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
%else:

View file

@ -11,7 +11,7 @@
<%def name="breadcrumbs_links()">
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository groups'),h.url('repo_groups'))}
${h.link_to(_('Repository groups'),h.route_path('repo_groups'))}
&raquo;
${_('Add Repository Group')}
</%def>
@ -27,7 +27,7 @@
${self.breadcrumbs()}
</div>
<!-- end box / title -->
${h.secure_form(h.url('repo_groups'), request=request)}
${h.secure_form(h.route_path('repo_group_create'), request=request)}
<div class="form">
<!-- fields -->
<div class="fields">

View file

@ -11,7 +11,7 @@
<%def name="breadcrumbs_links()">
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${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:
&raquo; ${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()">
<ul class="links">
<li>
<a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
<a href="${h.route_path('repo_group_new', _query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
</li>
</ul>
</%def>
@ -45,9 +45,9 @@
##main
<div class="sidebar">
<ul class="nav nav-pills nav-stacked">
<li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_repo_group', group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
<li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
<li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_repo_group_advanced', group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
<li class="${'active' if c.active=='settings' else ''}"><a href="${h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
<li class="${'active' if c.active=='permissions' else ''}"><a href="${h.route_path('edit_repo_group_perms', repo_group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
<li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('edit_repo_group_advanced', repo_group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
<li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
</ul>
</div>

View file

@ -28,7 +28,7 @@
<h3 class="panel-title">${_('Delete repository group')}</h3>
</div>
<div class="panel-body">
${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)}
<table class="display">
<tr>

View file

@ -5,14 +5,14 @@
<h3 class="panel-title">${_('Repository Group Permissions')}</h3>
</div>
<div class="panel-body">
${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)}
<table id="permissions_manage" class="rctable permissions">
<tr>
<th class="td-radio">${_('None')}</th>
<th class="td-radio">${_('Read')}</th>
<th class="td-radio">${_('Write')}</th>
<th class="td-radio">${_('Admin')}</th>
<th class="td-user">${_('User/User Group')}</th>
<th class="td-owner">${_('User/User Group')}</th>
<th></th>
</tr>
## USERS
@ -25,7 +25,6 @@
<td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
<td class="td-user">
${base.gravatar(_user.email, 16)}
<span class="user">
${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
</span>
</td>
<td></td>
</tr>
%else:
##forbid revoking permission from yourself, except if you're an super admin
<tr>
##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:
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
<td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
<td class="td-user">
${base.gravatar(_user.email, 16)}
<span class="user">
@ -89,10 +87,10 @@
## USER GROUPS
%for _user_group in c.repo_group.permission_user_groups():
<tr id="id${id(_user_group.users_group_name)}">
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
<td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
<td class="td-componentname">
<i class="icon-group" ></i>
%if h.HasPermissionAny('hg.admin')():

View file

@ -6,7 +6,7 @@
<h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
</div>
<div class="panel-body">
${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)}
<div class="form">
<!-- fields -->
<div class="fields">
@ -15,13 +15,26 @@
<label for="group_name">${_('Group Name')}:</label>
</div>
<div class="input">
${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}
</div>
</div>
<div class="field">
<div class="label">
<label for="repo_group">${_('Group parent')}:</label>
</div>
<div class="select">
${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
${c.form.render_error(request, c.form['repo_group'])|n}
<p class="help-block">${_('Optional select a parent group to move this repository group into.')}</p>
</div>
</div>
<div class="field badged-field">
<div class="label">
<label for="user">${_('Owner')}:</label>
<label for="repo_group_owner">${_('Owner')}:</label>
</div>
<div class="input">
<div class="badge-input-container">
@ -29,20 +42,22 @@
${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
</div>
<div class="badge-input-wrap">
${h.text('user', class_="medium", autocomplete="off")}
${c.form['repo_group_owner'].render(css_class='medium', oid='repo_group_owner')|n}
</div>
</div>
<form:error name="user"/>
${c.form.render_error(request, c.form['repo_group_owner'])|n}
<p class="help-block">${_('Change owner of this repository group.')}</p>
</div>
</div>
<div class="field">
<div class="label label-textarea">
<label for="group_description">${_('Description')}:</label>
<label for="repo_group_description">${_('Description')}:</label>
</div>
<div class="textarea text-area editor">
${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('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
<span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
<span id="meta-tags-desc" style="display: none">
@ -52,23 +67,18 @@
</div>
</div>
<div class="field">
<div class="label">
<label for="group_parent_id">${_('Group parent')}:</label>
</div>
<div class="select">
${h.select('group_parent_id','',c.repo_groups,class_="medium")}
</div>
</div>
<div class="field">
<div class="label label-checkbox">
<label for="enable_locking">${_('Enable Repository Locking')}:</label>
<label for="repo_group_enable_locking">${_('Enable Repository Locking')}:</label>
</div>
<div class="checkboxes">
${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}
<span class="help-block">${_('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.')}</span>
</div>
</div>
<div class="buttons">
${h.submit('save',_('Save'),class_="btn")}
${h.reset('reset',_('Reset'),class_="btn")}
@ -80,11 +90,6 @@
</div>
<script>
$(document).ready(function(){
$("#group_parent_id").select2({
'containerCssClass': "drop-menu",
'dropdownCssClass': "drop-menu-dropdown",
'dropdownAutoWidth': true
});
UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
UsersAutoComplete('repo_group_owner', '${c.rhodecode_user.user_id}');
})
</script>

View file

@ -24,7 +24,7 @@
<ul class="links">
%if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
<li>
<a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
<a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
</li>
%endif
</ul>

View file

@ -74,7 +74,7 @@
<ul class="admin_menu submenu">
<li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
<li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
<li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
<li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
<li><a href="${h.route_path('users')}">${_('Users')}</a></li>
<li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
<li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
@ -148,7 +148,7 @@
<li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
%endif
%if repository_groups:
<li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
<li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
%endif
%if user_groups:
<li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>

View file

@ -209,7 +209,7 @@
%if section == 'repositories':
<a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
%elif section == 'repositories_groups':
<a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
<a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
%elif section == 'user_groups':
##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
%endif

View file

@ -237,10 +237,10 @@
<%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
<div class="grid_edit">
<a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
<a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
</div>
<div class="grid_delete">
${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete', request=request)}
${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
${h.end_form()}

View file

@ -31,7 +31,7 @@
%endif
%if is_admin or create_repo_group:
<a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
<a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
%endif
%else:
##we're inside other repository group other terms apply
@ -39,10 +39,10 @@
<a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
%endif
%if is_admin or group_admin:
<a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
<a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
%endif
%if is_admin or group_admin:
<a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
<a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
%endif
%endif
</div>

View file

@ -1,79 +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 <http://www.gnu.org/licenses/>.
#
# 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 mock
import pylons
import pytest
from pylons import tmpl_context as c
@pytest.fixture
def current_user(request, user_util):
user = user_util.create_user()
request_mock = mock.Mock(user=user)
pylons.request._push_object(request_mock)
@request.addfinalizer
def cleanup():
pylons.request._pop_object()
return user
@pytest.fixture
def personal_group_with_parent(user_util, current_user):
group_read_only = user_util.create_repo_group()
user_util.grant_user_permission_to_repo_group(
group_read_only, current_user, 'group.read')
# TODO: johbo: This should go into the business models
group_as_admin = user_util.create_repo_group(
owner=current_user)
group_as_admin.parent_group = group_read_only
group_as_admin.group_name = group_as_admin.get_new_name(
group_as_admin.group_name)
return group_as_admin
@pytest.fixture
def controller():
from rhodecode.controllers.admin import repo_groups
return repo_groups.RepoGroupsController()
def test_repo_groups_load_defaults(
current_user, personal_group_with_parent, controller):
personal_group = personal_group_with_parent
controller._RepoGroupsController__load_defaults(True, personal_group)
expected_list = [-1, personal_group.parent_group.group_id]
returned_group_ids = [group[0] for group in c.repo_groups]
assert returned_group_ids == expected_list
def test_repo_groups_load_defaults_with_missing_group(
current_user, personal_group_with_parent, controller):
personal_group = personal_group_with_parent
controller._RepoGroupsController__load_defaults(True)
expected_list = sorted([-1, personal_group.group_id])
returned_group_ids = sorted([group[0] for group in c.repo_groups])
assert returned_group_ids == expected_list

View file

@ -20,7 +20,7 @@
import pytest
from rhodecode.tests import TestController, url
from rhodecode.tests import TestController
from rhodecode.tests.fixture import Fixture
@ -32,6 +32,8 @@ def route_path(name, params=None, **kwargs):
'home': '/',
'repos':
ADMIN_PREFIX + '/repos',
'repo_groups':
ADMIN_PREFIX + '/repo_groups',
'user_groups':
ADMIN_PREFIX + '/user_groups',
'user_groups_data':
@ -65,7 +67,7 @@ class TestAdminDelegatedUser(TestController):
response = self.app.get(route_path('repos'), status=200)
response.mustcontain('data: []')
response = self.app.get(url('repo_groups'), status=200)
response = self.app.get(route_path('repo_groups'), status=200)
response.mustcontain('data: []')
response = self.app.get(route_path('user_groups_data'),
@ -100,7 +102,7 @@ class TestAdminDelegatedUser(TestController):
response = self.app.get(route_path('repos'), status=200)
response.mustcontain('"name_raw": "{}"'.format(repo_name))
response = self.app.get(url('repo_groups'), status=200)
response = self.app.get(route_path('repo_groups'), status=200)
response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
response = self.app.get(route_path('user_groups_data'),
@ -142,7 +144,7 @@ class TestAdminDelegatedUser(TestController):
response = self.app.get(route_path('repos'), status=200)
response.mustcontain('"name_raw": "{}"'.format(repo_name))
response = self.app.get(url('repo_groups'), status=200)
response = self.app.get(route_path('repo_groups'), status=200)
response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
response = self.app.get(route_path('user_groups_data'),

View file

@ -37,7 +37,6 @@ from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.scm import ScmModel
from rhodecode.model.settings import UiSetting, SettingsModel
from rhodecode.tests.fixture import Fixture
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
fixture = Fixture()
@ -221,23 +220,6 @@ def platform_encodes_filenames():
return path_with_latin1 != read_path
@pytest.fixture
def repo_groups(request):
session = meta.Session()
zombie_group = fixture.create_repo_group('zombie')
parent_group = fixture.create_repo_group('parent')
child_group = fixture.create_repo_group('parent/child')
groups_in_db = session.query(db.RepoGroup).all()
assert len(groups_in_db) == 3
assert child_group.group_parent_id == parent_group.group_id
@request.addfinalizer
def cleanup():
fixture.destroy_repo_group(zombie_group)
fixture.destroy_repo_group(child_group)
fixture.destroy_repo_group(parent_group)
return (zombie_group, parent_group, child_group)
def test_repo2db_mapper_groups(repo_groups):

View file

@ -59,7 +59,7 @@ def _update_repo(name, **kwargs):
return r
class TestRepoGroups:
class TestRepoGroups(object):
@pytest.fixture(autouse=True)
def prepare(self, request):

View file

@ -1830,3 +1830,29 @@ def disable_anonymous_user(request, pylonsapp):
@request.addfinalizer
def cleanup():
set_anonymous_access(True)
@pytest.fixture
def rc_fixture(request):
return Fixture()
@pytest.fixture
def repo_groups(request):
fixture = Fixture()
session = Session()
zombie_group = fixture.create_repo_group('zombie')
parent_group = fixture.create_repo_group('parent')
child_group = fixture.create_repo_group('parent/child')
groups_in_db = session.query(RepoGroup).all()
assert len(groups_in_db) == 3
assert child_group.group_parent_id == parent_group.group_id
@request.addfinalizer
def cleanup():
fixture.destroy_repo_group(zombie_group)
fixture.destroy_repo_group(child_group)
fixture.destroy_repo_group(parent_group)
return zombie_group, parent_group, child_group