diff --git a/seahub/api2/endpoints/dir.py b/seahub/api2/endpoints/dir.py index 7d8b6433b8..c6c0a1fdac 100644 --- a/seahub/api2/endpoints/dir.py +++ b/seahub/api2/endpoints/dir.py @@ -110,10 +110,12 @@ class DirView(APIView): return get_dir_entrys_by_id(request, repo, path, dir_id, request_type) def post(self, request, repo_id, format=None): - """ Create, rename dir. + """ Create, rename, revert dir. Permission checking: - 1. user with 'rw' permission. + 1. create: user with 'rw' permission for current dir's parent dir; + 2. rename: user with 'rw' permission for current dir; + 3. revert: user with 'rw' permission for current dir's parent dir; """ # argument check @@ -123,7 +125,7 @@ class DirView(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) if path == '/': - error_msg = 'Can not make or rename root dir.' + error_msg = 'Can not operate root dir.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = request.data.get('operation', None) @@ -132,8 +134,8 @@ class DirView(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = operation.lower() - if operation not in ('mkdir', 'rename'): - error_msg = "operation can only be 'mkdir', 'rename'." + if operation not in ('mkdir', 'rename', 'revert'): + error_msg = "operation can only be 'mkdir', 'rename' or 'revert'." return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check @@ -211,6 +213,32 @@ class DirView(APIView): error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + if operation == 'revert': + commit_id = request.data.get('commit_id', None) + if not commit_id: + error_msg = 'commit_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if seafile_api.get_dir_id_by_path(repo_id, path): + # dir exists in repo + if check_folder_permission(request, repo_id, path) != 'rw': + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + else: + # dir NOT exists in repo + if check_folder_permission(request, repo_id, '/') != 'rw': + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + seafile_api.revert_dir(repo_id, commit_id, path, username) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) + def delete(self, request, repo_id, format=None): """ Delete dir. diff --git a/seahub/api2/endpoints/file.py b/seahub/api2/endpoints/file.py index 750ebdada0..28af56ee9d 100644 --- a/seahub/api2/endpoints/file.py +++ b/seahub/api2/endpoints/file.py @@ -9,6 +9,8 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status +from django.utils.translation import ugettext as _ + from seahub.api2.throttling import UserRateThrottle from seahub.api2.authentication import TokenAuthentication from seahub.api2.utils import api_error @@ -92,13 +94,14 @@ class FileView(APIView): return Response(file_info) def post(self, request, repo_id, format=None): - """ Create, rename, move, copy file + """ Create, rename, move, copy, revert file Permission checking: 1. create: user with 'rw' permission for current parent dir; 2. rename: user with 'rw' permission for current file; 3. move : user with 'rw' permission for current file, 'rw' permission for dst parent dir; 4. copy : user with 'r' permission for current file, 'rw' permission for dst parent dir; + 4. revert: user with 'rw' permission for current file's parent dir; """ # argument check @@ -113,8 +116,8 @@ class FileView(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = operation.lower() - if operation not in ('create', 'rename', 'move', 'copy'): - error_msg = "operation can only be 'create', 'rename', 'move' or 'copy'." + if operation not in ('create', 'rename', 'move', 'copy', 'revert'): + error_msg = "operation can only be 'create', 'rename', 'move', 'copy' or 'revert'." return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check @@ -346,6 +349,42 @@ class FileView(APIView): dst_file_info = self.get_file_info(username, dst_repo_id, dst_file_path) return Response(dst_file_info) + if operation == 'revert': + commit_id = request.data.get('commit_id', None) + if not commit_id: + error_msg = 'commit_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if seafile_api.get_file_id_by_path(repo_id, path): + # file exists in repo + if check_folder_permission(request, repo_id, parent_dir) != 'rw': + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + if (is_locked, locked_by_me) == (None, None): + error_msg = _("Check file lock error") + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + if is_locked and not locked_by_me: + error_msg = _("File is locked") + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + else: + # file NOT exists in repo + if check_folder_permission(request, repo_id, '/') != 'rw': + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + seafile_api.revert_file(repo_id, commit_id, path, username) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) + def put(self, request, repo_id, format=None): """ Currently only for lock and unlock file operation. diff --git a/seahub/templates/file_revisions.html b/seahub/templates/file_revisions.html index 2f6346a30b..3079977a79 100644 --- a/seahub/templates/file_revisions.html +++ b/seahub/templates/file_revisions.html @@ -70,7 +70,7 @@ {% if commit.id != repo.head_cmmt_id %} {% if can_revert_file %} - {% trans 'Restore' %} + {% trans 'Restore' %} {% endif %} {% endif %} {% trans 'Download' %} @@ -86,6 +86,26 @@ {% block extra_script %} {% endblock %} diff --git a/seahub/templates/repo_history_view.html b/seahub/templates/repo_history_view.html index 6e65bc82ca..51b450761b 100644 --- a/seahub/templates/repo_history_view.html +++ b/seahub/templates/repo_history_view.html @@ -64,7 +64,7 @@ {% trans {{ dirent.obj_name }} - {% trans "Restore" %} + {% trans "Restore" %} {% endfor %} @@ -74,7 +74,7 @@ {{ dirent.props.obj_name }} {{ dirent.file_size|filesizeformat }} - {% trans "Restore" %} + {% trans "Restore" %} {% trans "Download" %} @@ -90,6 +90,33 @@ addConfirmTo($('#repo-revert'), { 'post':true }); -addFormPost($('.restore-file, .restore-dir')); +$('.restore-file, .restore-dir').click(function() { + var _this = $(this), + commit_id = _this.attr('data-commit_id'), + path = _this.attr('data-commit_path'), + url; + + if (_this.hasClass('restore-file')) { + url = "{% url 'api-v2.1-file-view' repo.id %}" + "?p=" + encodeURIComponent(path); + } else { + url = "{% url 'api-v2.1-dir-view' repo.id %}" + "?p=" + encodeURIComponent(path); + } + + $.ajax({ + url: url, + type: 'POST', + data: {'operation': 'revert', 'commit_id': commit_id}, + cache: false, + dataType: 'json', + beforeSend: prepareCSRFToken, + success: function() { + _this.closest('tr').remove(); + feedback("{% trans "Success" %}", 'success'); + }, + error: ajaxErrorHandler + }); + return false; +}); + {% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index 4c334ae0d7..5df6598786 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -82,9 +82,7 @@ urlpatterns = patterns( # url(r'^home/public/reply/(?P[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'), # url(r'^home/owner/(?P[^/]+)/$', ownerhome, name='ownerhome'), - # revert file/dir/repo - url(r'^repo/revert_file/(?P[-0-9a-f]{36})/$', repo_revert_file, name='repo_revert_file'), - url(r'^repo/revert_dir/(?P[-0-9a-f]{36})/$', repo_revert_dir, name='repo_revert_dir'), + # revert repo url(r'^repo/history/revert/(?P[-0-9a-f]{36})/$', repo_revert_history, name='repo_revert_history'), (r'^repo/upload_check/$', validate_filename), diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 47023c9ca0..d9f921c20b 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -936,94 +936,6 @@ def render_file_revisions (request, repo_id): 'days': days, }, context_instance=RequestContext(request)) -@login_required -@require_POST -def repo_revert_file(request, repo_id): - repo = get_repo(repo_id) - if not repo: - raise Http404 - - commit_id = request.GET.get('commit') - path = request.GET.get('p') - - if not (commit_id and path): - return render_error(request, _(u"Invalid arguments")) - - referer = request.META.get('HTTP_REFERER', None) - next = settings.SITE_ROOT if referer is None else referer - - username = request.user.username - # perm check - if check_folder_permission(request, repo.id, path) != 'rw': - messages.error(request, _("Permission denied")) - return HttpResponseRedirect(next) - - is_locked, locked_by_me = check_file_lock(repo_id, path, username) - if (is_locked, locked_by_me) == (None, None): - messages.error(request, _("Check file lock error")) - return HttpResponseRedirect(next) - - if is_locked and not locked_by_me: - messages.error(request, _("File is locked")) - return HttpResponseRedirect(next) - - try: - ret = seafile_api.revert_file(repo_id, commit_id, path, username) - except Exception as e: - logger.error(e) - messages.error(request, _('Failed to restore, please try again later.')) - return HttpResponseRedirect(next) - - if ret == 1: - root_url = reverse('view_common_lib_dir', args=[repo_id, '']) - msg = _(u'Successfully revert %(path)s to root directory.') % {"path": escape(path.lstrip('/')), "root": root_url} - messages.success(request, msg, extra_tags='safe') - else: - file_view_url = reverse('view_lib_file', args=[repo_id, path.encode('utf-8')]) - msg = _(u'Successfully revert %(path)s') % {"url": file_view_url, "path": escape(path.lstrip('/'))} - messages.success(request, msg, extra_tags='safe') - - return HttpResponseRedirect(next) - -@login_required -@require_POST -def repo_revert_dir(request, repo_id): - repo = get_repo(repo_id) - if not repo: - raise Http404 - - commit_id = request.GET.get('commit') - path = request.GET.get('p') - - if not (commit_id and path): - return render_error(request, _(u"Invalid arguments")) - - referer = request.META.get('HTTP_REFERER', None) - next = settings.SITE_ROOT if referer is None else referer - - # perm check - if check_folder_permission(request, repo.id, path) != 'rw': - messages.error(request, _("Permission denied")) - return HttpResponseRedirect(next) - - try: - ret = seafile_api.revert_dir(repo_id, commit_id, path, request.user.username) - except Exception as e: - logger.error(e) - messages.error(request, _('Failed to restore, please try again later.')) - return HttpResponseRedirect(next) - - if ret == 1: - root_url = reverse('view_common_lib_dir', args=[repo_id, '']) - msg = _(u'Successfully revert %(path)s to root directory.') % {"path": escape(path.lstrip('/')), "url": root_url} - messages.success(request, msg, extra_tags='safe') - else: - dir_view_url = reverse('view_common_lib_dir', args=[repo_id, path.strip('/').encode('utf-8')]) - msg = _(u'Successfully revert %(path)s') % {"url": dir_view_url, "path": escape(path.lstrip('/'))} - messages.success(request, msg, extra_tags='safe') - - return HttpResponseRedirect(next) - @login_required def file_revisions(request, repo_id): """List file revisions in file version history page. diff --git a/tests/api/endpoints/test_dir_view.py b/tests/api/endpoints/test_dir_view.py index f1c437277d..fda1bab488 100644 --- a/tests/api/endpoints/test_dir_view.py +++ b/tests/api/endpoints/test_dir_view.py @@ -235,6 +235,76 @@ class DirViewTest(BaseTestCase): resp = self.client.post(self.url + '?p=' + self.folder_path, data) self.assertEqual(403, resp.status_code) + def test_can_revert_folder(self): + self.login_as(self.user) + + # first rename dir + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.folder_name, + new_name, self.user_name) + new_dir_path = '/' + new_name + + # get commit list + commits = seafile_api.get_commit_list(self.repo_id, -1, -1) + + # then revert dir + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_dir_path, data) + + self.assertEqual(200, resp.status_code) + + def test_revert_folder_with_invalid_user_permission(self): + # first rename dir + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.folder_name, + new_name, self.user_name) + new_dir_path = '/' + new_name + + # get commit list + commits = seafile_api.get_commit_list(self.repo_id, -1, -1) + + # then revert dir + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_dir_path, data) + self.assertEqual(403, resp.status_code) + + def test_revert_folder_with_r_permission(self): + # first rename dir + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.folder_name, + new_name, self.user_name) + new_dir_path = '/' + new_name + + # get commit list + commits = seafile_api.get_commit_list(self.repo_id, -1, -1) + + self.share_repo_to_admin_with_r_permission() + self.login_as(self.admin) + + # then revert dir + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_dir_path, data) + self.assertEqual(403, resp.status_code) + + def test_revert_folder_without_commit_id(self): + self.login_as(self.user) + + # then revert dir + data = { + 'operation': 'revert', + } + resp = self.client.post(self.url + '?p=' + self.folder_path, data) + self.assertEqual(400, resp.status_code) + # for test http DELETE request def test_can_delete_folder(self): self.login_as(self.user) diff --git a/tests/api/endpoints/test_file_view.py b/tests/api/endpoints/test_file_view.py index 07a46b8df6..8c6231b858 100644 --- a/tests/api/endpoints/test_file_view.py +++ b/tests/api/endpoints/test_file_view.py @@ -464,6 +464,76 @@ class FileViewTest(BaseTestCase): resp = self.client.post(url + '?p=/' + admin_file_name, data) self.assertEqual(403, resp.status_code) + def test_can_revert_file(self): + self.login_as(self.user) + + # first rename file + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.file_name, + new_name, self.user_name) + new_file_path = '/' + new_name + + # get file revisions + commits = seafile_api.get_file_revisions( + self.repo_id, new_file_path, -1, -1, 100) + + # then revert file + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_file_path, data) + + self.assertEqual(200, resp.status_code) + + def test_revert_file_with_invalid_user_permission(self): + # first rename file + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.file_name, + new_name, self.user_name) + new_file_path = '/' + new_name + + # get file revisions + commits = seafile_api.get_file_revisions( + self.repo_id, new_file_path, -1, -1, 100) + + # then revert file + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_file_path, data) + self.assertEqual(403, resp.status_code) + + def test_revert_file_with_r_permission(self): + # first rename file + new_name = randstring(6) + seafile_api.rename_file(self.repo_id, '/', self.file_name, + new_name, self.user_name) + new_file_path = '/' + new_name + + # get file revisions + commits = seafile_api.get_file_revisions( + self.repo_id, new_file_path, -1, -1, 100) + + self.share_repo_to_admin_with_r_permission() + self.login_as(self.admin) + # then revert file + data = { + 'operation': 'revert', + 'commit_id': commits[0].id + } + resp = self.client.post(self.url + '?p=' + new_file_path, data) + self.assertEqual(403, resp.status_code) + + def test_revert_file_without_commit_id(self): + self.login_as(self.user) + data = { + 'operation': 'revert', + } + resp = self.client.post(self.url + '?p=' + self.file_path, data) + self.assertEqual(400, resp.status_code) + # for test http PUT request def test_can_lock_file(self):