# HG changeset patch # User Marcin Kuzminski # Date 2017-05-05 14:04:16 # Node ID ddacc55964a31ad99daa87abc20f992203a61939 # Parent 3b110c63e6965981295a1447551c1421014335bf repo-permissions: moved permissions into pyramid. - fix issues when added new perms changed those already in the form - fixed issue with default user overriding permissions when private repos where turned on. - Two above are not security issues, but rather making the behaviour more consistent. 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 @@ -31,6 +31,11 @@ def includeme(config): name='edit_repo_caches', pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True) + # Permissions + config.add_route( + name='edit_repo_perms', + pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) + # Repo Review Rules config.add_route( name='repo_reviewers', 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 @@ -39,6 +39,7 @@ def route_path(name, params=None, **kwar base_url = { 'edit_repo': '/{repo_name}/settings', 'edit_repo_caches': '/{repo_name}/settings/caches', + 'edit_repo_perms': '/{repo_name}/settings/permissions', }[name].format(**kwargs) if params: @@ -59,7 +60,8 @@ def _get_permission_for_user(user, repo) class TestAdminRepoSettings(object): @pytest.mark.parametrize('urlname', [ 'edit_repo', - 'edit_repo_caches' + 'edit_repo_caches', + 'edit_repo_perms', ]) def test_show_page(self, urlname, app, backend): app.get(route_path(urlname, repo_name=backend.repo_name), status=200) @@ -72,7 +74,6 @@ class TestAdminRepoSettings(object): self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name)) @pytest.mark.parametrize('urlname', [ - 'edit_repo_perms', 'edit_repo_advanced', 'repo_vcs_settings', 'edit_repo_fields', diff --git a/rhodecode/apps/repository/views/repo_permissions.py b/rhodecode/apps/repository/views/repo_permissions.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_permissions.py @@ -0,0 +1,98 @@ +# -*- 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.httpexceptions import HTTPFound +from pyramid.view import view_config + +from rhodecode.apps._base import RepoAppView +from rhodecode.forms import RcForm +from rhodecode.lib import helpers as h +from rhodecode.lib import audit_logger +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, + HasRepoPermissionAllDecorator, CSRFRequired) +from rhodecode.model.db import RepositoryField, RepoGroup +from rhodecode.model.forms import RepoPermsForm +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel +from rhodecode.model.scm import RepoGroupList, ScmModel +from rhodecode.model.validation_schema.schemas import repo_schema + +log = logging.getLogger(__name__) + + +class RepoSettingsPermissionsView(RepoAppView): + + def load_default_context(self): + c = self._get_local_tmpl_context() + + # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead + c.repo_info = self.db_repo + + self._register_global_c(c) + return c + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_perms', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def edit_permissions(self): + c = self.load_default_context() + c.active = 'permissions' + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAllDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_perms', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def edit_permissions_update(self): + _ = self.request.translate + c = self.load_default_context() + c.active = 'permissions' + data = self.request.POST + # store private flag outside of HTML to verify if we can modify + # default user permissions, prevents submition of FAKE post data + # into the form for private repos + data['repo_private'] = self.db_repo.private + form = RepoPermsForm()().to_python(data) + changes = RepoModel().update_permissions( + self.db_repo_name, form['perm_additions'], form['perm_updates'], + form['perm_deletions']) + + action_data = { + 'added': changes['added'], + 'updated': changes['updated'], + 'deleted': changes['deleted'], + } + audit_logger.store( + 'repo.edit.permissions', action_data=action_data, + user=self._rhodecode_user, repo=self.db_repo) + + Session().commit() + h.flash(_('Repository permissions updated'), category='success') + + raise HTTPFound( + self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name)) diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -661,16 +661,6 @@ def make_map(config): requirements=URL_NAME_REQUIREMENTS) # repo edit options - rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions', - jsroute=True, - controller='admin/repos', action='edit_permissions', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions', - controller='admin/repos', action='edit_permissions_update', - conditions={'method': ['PUT'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', controller='admin/repos', action='edit_fields', conditions={'method': ['GET'], 'function': check_repo}, diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -339,33 +339,6 @@ class ReposController(BaseRepoController # url('repo', repo_name=ID) @HasRepoPermissionAllDecorator('repository.admin') - def edit_permissions(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - c.repo_info = self._load_repo(repo_name) - c.active = 'permissions' - defaults = RepoModel()._get_defaults(repo_name) - - return htmlfill.render( - render('admin/repos/repo_edit.mako'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False) - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def edit_permissions_update(self, repo_name): - form = RepoPermsForm()().to_python(request.POST) - RepoModel().update_permissions(repo_name, - form['perm_additions'], form['perm_updates'], form['perm_deletions']) - - #TODO: implement this - #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions', - # repo_name, self.ip_addr, self.sa) - Session().commit() - h.flash(_('Repository permissions updated'), category='success') - return redirect(url('edit_repo_perms', repo_name=repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') def edit_fields(self, repo_name): """GET /repo_name/settings: Form to edit an existing item""" c.repo_info = self._load_repo(repo_name) diff --git a/rhodecode/lib/audit_logger.py b/rhodecode/lib/audit_logger.py --- a/rhodecode/lib/audit_logger.py +++ b/rhodecode/lib/audit_logger.py @@ -36,6 +36,7 @@ ACTIONS = { 'repo.add': {}, 'repo.edit': {}, + 'repo.edit.permissions': {}, 'repo.commit.strip': {} } diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -259,7 +259,7 @@ class RepoModel(BaseModel): defaults = repo_info.get_dict() defaults['repo_name'] = repo_info.just_name - groups = repo_info.groups_with_parents + groups = repo_info.groups_with_parents parent_group = groups[-1] if groups else None # we use -1 as this is how in HTML, we mark an empty group @@ -295,16 +295,6 @@ class RepoModel(BaseModel): replacement_user = User.get_first_super_admin().username defaults.update({'user': replacement_user}) - # fill repository users - for p in repo_info.repo_to_perm: - defaults.update({'u_perm_%s' % p.user.user_id: - p.permission.permission_name}) - - # fill repository groups - for p in repo_info.users_group_to_perm: - defaults.update({'g_perm_%s' % p.users_group.users_group_id: - p.permission.permission_name}) - return defaults def update(self, repo, **kwargs): @@ -507,10 +497,16 @@ class RepoModel(BaseModel): req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') + changes = { + 'added': [], + 'updated': [], + 'deleted': [] + } # update permissions for member_id, perm, member_type in perm_updates: member_id = int(member_id) if member_type == 'user': + member_name = User.get(member_id).username # this updates also current one if found self.grant_user_permission( repo=repo, user=member_id, perm=perm) @@ -522,10 +518,14 @@ class RepoModel(BaseModel): self.grant_user_group_permission( repo=repo, group_name=member_id, perm=perm) + changes['updated'].append({'type': member_type, 'id': member_id, + 'name': member_name, 'new_perm': perm}) + # set new permissions for member_id, perm, member_type in perm_additions: member_id = int(member_id) if member_type == 'user': + member_name = User.get(member_id).username self.grant_user_permission( repo=repo, user=member_id, perm=perm) else: # set for user group @@ -535,11 +535,13 @@ class RepoModel(BaseModel): *req_perms)(member_name, user=cur_user): self.grant_user_group_permission( repo=repo, group_name=member_id, perm=perm) - + changes['added'].append({'type': member_type, 'id': member_id, + 'name': member_name, 'new_perm': perm}) # delete permissions for member_id, perm, member_type in perm_deletions: member_id = int(member_id) if member_type == 'user': + member_name = User.get(member_id).username self.revoke_user_permission(repo=repo, user=member_id) else: # set for user group # check if we have permissions to alter this usergroup @@ -549,6 +551,10 @@ class RepoModel(BaseModel): self.revoke_user_group_permission( repo=repo, group_name=member_id) + changes['deleted'].append({'type': member_type, 'id': member_id, + 'name': member_name, 'new_perm': perm}) + return changes + def create_fork(self, form_data, cur_user): """ Simple wrapper into executing celery task for fork creation diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py --- a/rhodecode/model/validators.py +++ b/rhodecode/model/validators.py @@ -784,7 +784,8 @@ def ValidPerms(type_='repo'): del_member = perm_dict.get('id') del_type = perm_dict.get('type') if del_member and del_type: - perm_deletions.add((del_member, None, del_type)) + perm_deletions.add( + (del_member, None, del_type)) # store additions in order of how they were added in web form for k in sorted(new_perms_group.keys()): @@ -793,36 +794,48 @@ def ValidPerms(type_='repo'): new_type = perm_dict.get('type') new_perm = perm_dict.get('perm') if new_member and new_perm and new_type: - perm_additions.add((new_member, new_perm, new_type)) + perm_additions.add( + (new_member, new_perm, new_type)) # get updates of permissions # (read the existing radio button states) + default_user_id = User.get_default_user().user_id for k, update_value in value.iteritems(): if k.startswith('u_perm_') or k.startswith('g_perm_'): member = k[7:] update_type = {'u': 'user', 'g': 'users_group'}[k[0]] - if member == User.DEFAULT_USER: + + if safe_int(member) == default_user_id: if str2bool(value.get('repo_private')): - # set none for default when updating to - # private repo protects agains form manipulation + # prevent from updating default user permissions + # when this repository is marked as private update_value = EMPTY_PERM - perm_updates.add((member, update_value, update_type)) - # check the deletes - value['perm_additions'] = list(perm_additions) + perm_updates.add( + (member, update_value, update_type)) + + value['perm_additions'] = [] # propagated later value['perm_updates'] = list(perm_updates) value['perm_deletions'] = list(perm_deletions) - # validate users they exist and they are active ! - for member_id, _perm, member_type in perm_additions: + updates_map = dict( + (x[0], (x[1], x[2])) for x in value['perm_updates']) + # make sure Additions don't override updates. + for member_id, perm, member_type in list(perm_additions): + if member_id in updates_map: + perm = updates_map[member_id][0] + value['perm_additions'].append((member_id, perm, member_type)) + + # on new entries validate users they exist and they are active ! + # this leaves feedback to the form try: if member_type == 'user': - self.user_db = User.query()\ + User.query()\ .filter(User.active == true())\ .filter(User.user_id == member_id).one() if member_type == 'users_group': - self.user_db = UserGroup.query()\ + UserGroup.query()\ .filter(UserGroup.users_group_active == true())\ .filter(UserGroup.users_group_id == member_id)\ .one() 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 @@ -24,7 +24,6 @@ function registerRCRoutes() { pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']); pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); - pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); @@ -99,6 +98,7 @@ function registerRCRoutes() { pyroutes.register('goto_switcher_data', '/_goto_data', []); pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']); + pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']); pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']); diff --git a/rhodecode/templates/admin/repos/repo_edit.mako b/rhodecode/templates/admin/repos/repo_edit.mako --- a/rhodecode/templates/admin/repos/repo_edit.mako +++ b/rhodecode/templates/admin/repos/repo_edit.mako @@ -42,7 +42,7 @@ ${_('Settings')}
  • - ${_('Permissions')} + ${_('Permissions')}
  • ${_('Advanced')} diff --git a/rhodecode/templates/admin/repos/repo_edit_permissions.mako b/rhodecode/templates/admin/repos/repo_edit_permissions.mako --- a/rhodecode/templates/admin/repos/repo_edit_permissions.mako +++ b/rhodecode/templates/admin/repos/repo_edit_permissions.mako @@ -5,8 +5,7 @@

    ${_('Repository Permissions')}

    - ${h.secure_form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')} - ${h.hidden('repo_private')} + ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST')} @@ -40,7 +39,7 @@ %else: - - - - + + + + - - - - + + + +
    ${_('None')}
    - ${_('private repository')} + ${_('private repository')} @@ -50,10 +49,10 @@
    ${h.radio('u_perm_%s' % _user.user_id,'repository.none')}${h.radio('u_perm_%s' % _user.user_id,'repository.read')}${h.radio('u_perm_%s' % _user.user_id,'repository.write')}${h.radio('u_perm_%s' % _user.user_id,'repository.admin')}${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')} ${base.gravatar(_user.email, 16)} @@ -79,10 +78,10 @@ ## USER GROUPS %for _user_group in c.repo_info.permission_user_groups():
    ${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')} %if h.HasPermissionAny('hg.admin')(): diff --git a/rhodecode/templates/base/perms_summary.mako b/rhodecode/templates/base/perms_summary.mako --- a/rhodecode/templates/base/perms_summary.mako +++ b/rhodecode/templates/base/perms_summary.mako @@ -146,7 +146,7 @@ %if actions: %if section == 'repositories': - ${_('edit')} + ${_('edit')} %elif section == 'repositories_groups': ${_('edit')} %elif section == 'user_groups':