diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {} EXTENSIONS = {} __version__ = ('.'.join((str(each) for each in VERSION[:3]))) -__dbversion__ = 105 # defines current db version for migrations +__dbversion__ = 106 # defines current db version for migrations __platform__ = platform.system() __license__ = 'AGPLv3, and Commercial License' __author__ = 'RhodeCode GmbH' diff --git a/rhodecode/apps/debug_style/views.py b/rhodecode/apps/debug_style/views.py --- a/rhodecode/apps/debug_style/views.py +++ b/rhodecode/apps/debug_style/views.py @@ -220,7 +220,7 @@ def db(): 'pr_comment_url': 'http://comment-url', 'pr_comment_reply_url': 'http://comment-url#reply', - 'comment_file': 'rhodecode/model/db.py', + 'comment_file': 'rhodecode/model/get_flow_commits', 'comment_line': 'o1210', 'comment_type': 'todo', 'comment_body': ''' diff --git a/rhodecode/apps/repository/tests/test_repo_commit_comments.py b/rhodecode/apps/repository/tests/test_repo_commit_comments.py --- a/rhodecode/apps/repository/tests/test_repo_commit_comments.py +++ b/rhodecode/apps/repository/tests/test_repo_commit_comments.py @@ -22,8 +22,7 @@ import pytest from rhodecode.tests import TestController -from rhodecode.model.db import ( - ChangesetComment, Notification, UserNotification) +from rhodecode.model.db import ChangesetComment, Notification from rhodecode.model.meta import Session from rhodecode.lib import helpers as h @@ -269,7 +268,36 @@ class TestRepoCommitCommentsView(TestCon repo_name=backend.repo_name, commit_id=commit_id)) assert_comment_links(response, 0, 0) - @pytest.mark.parametrize('renderer, input, output', [ + def test_delete_forbidden_for_immutable_comments(self, backend): + self.log_user() + commit_id = backend.repo.get_commit('300').raw_id + text = u'CommentOnCommit' + + params = {'text': text, 'csrf_token': self.csrf_token} + self.app.post( + route_path( + 'repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), + params=params) + + comments = ChangesetComment.query().all() + assert len(comments) == 1 + comment_id = comments[0].comment_id + + comment = ChangesetComment.get(comment_id) + comment.immutable_state = ChangesetComment.OP_IMMUTABLE + Session().add(comment) + Session().commit() + + self.app.post( + route_path('repo_commit_comment_delete', + repo_name=backend.repo_name, + commit_id=commit_id, + comment_id=comment_id), + params={'csrf_token': self.csrf_token}, + status=403) + + @pytest.mark.parametrize('renderer, text_input, output', [ ('rst', 'plain text', '

plain text

'), ('rst', 'header\n======', '

header

'), ('rst', '*italics*', 'italics'), @@ -280,11 +308,11 @@ class TestRepoCommitCommentsView(TestCon ('markdown', '**bold**', 'bold'), ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', 'md-header', 'md-italics', 'md-bold', ]) - def test_preview(self, renderer, input, output, backend, xhr_header): + def test_preview(self, renderer, text_input, output, backend, xhr_header): self.log_user() params = { 'renderer': renderer, - 'text': input, + 'text': text_input, 'csrf_token': self.csrf_token } commit_id = '0' * 16 # fake this for tests diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -22,7 +22,7 @@ import logging import collections -from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound +from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden from pyramid.view import view_config from pyramid.renderers import render from pyramid.response import Response @@ -538,6 +538,10 @@ class RepoCommitsView(RepoAppView): # comment already deleted in another call probably return True + if comment.immutable: + # don't allow deleting comments that are immutable + raise HTTPForbidden() + is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) super_admin = h.HasPermissionAny('hg.admin')() comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -1473,6 +1473,10 @@ class RepoPullRequestsView(RepoAppView, self.request.matchdict['comment_id']) comment_id = comment.comment_id + if comment.immutable: + # don't allow deleting comments that are immutable + raise HTTPForbidden() + if pull_request.is_closed(): log.debug('comment: forbidden because pull request is closed') raise HTTPForbidden() diff --git a/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py b/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import logging +from sqlalchemy import * + +from alembic.migration import MigrationContext +from alembic.operations import Operations +from sqlalchemy import BigInteger + +from rhodecode.lib.dbmigrate.versions import _reset_base +from rhodecode.model import init_model_encryption + + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_4_18_0_1 as db + + init_model_encryption(db) + + context = MigrationContext.configure(migrate_engine.connect()) + op = Operations(context) + + comments = db.ChangesetComment.__table__ + + with op.batch_alter_table(comments.name) as batch_op: + new_column = Column('immutable_state', Unicode(128), nullable=True) + batch_op.add_column(new_column) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + pass diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -3711,6 +3711,9 @@ class ChangesetComment(Base, BaseModel): COMMENT_TYPE_TODO = u'todo' COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO] + OP_IMMUTABLE = u'immutable' + OP_CHANGEABLE = u'changeable' + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) revision = Column('revision', String(40), nullable=True) @@ -3725,6 +3728,7 @@ class ChangesetComment(Base, BaseModel): modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) renderer = Column('renderer', Unicode(64), nullable=True) display_state = Column('display_state', Unicode(128), nullable=True) + immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE) comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) @@ -3767,6 +3771,10 @@ class ChangesetComment(Base, BaseModel): def outdated(self): return self.display_state == self.COMMENT_OUTDATED + @property + def immutable(self): + return self.immutable_state == self.OP_IMMUTABLE + def outdated_at_version(self, version): """ Checks if comment is outdated for given pull request version diff --git a/rhodecode/templates/changeset/changeset_file_comment.mako b/rhodecode/templates/changeset/changeset_file_comment.mako --- a/rhodecode/templates/changeset/changeset_file_comment.mako +++ b/rhodecode/templates/changeset/changeset_file_comment.mako @@ -135,7 +135,7 @@ ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())): ## permissions to delete - %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id: + %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): ## TODO: dan: add edit comment here ${_('Delete')} %else: