From 1548fb1ee5a51dc9246538aa6ecffac06d04f9c4 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Wed, 12 Sep 2018 18:09:56 +0800 Subject: [PATCH 01/26] [tests] Fix tests --- tests/api/endpoints/admin/test_org_stats.py | 3 +++ tests/seahub/views/ajax/test_get_file_upload_url_ul.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/api/endpoints/admin/test_org_stats.py b/tests/api/endpoints/admin/test_org_stats.py index 2851dad23b..0a60f1552a 100644 --- a/tests/api/endpoints/admin/test_org_stats.py +++ b/tests/api/endpoints/admin/test_org_stats.py @@ -10,6 +10,9 @@ except ImportError: class AdminOrgStatsTrafficTest(BaseTestCase): def test_get(self): + if not LOCAL_PRO_DEV_ENV: + return + self.login_as(self.admin) url = reverse('api-v2.1-admin-org-stats-traffic', args=[1]) diff --git a/tests/seahub/views/ajax/test_get_file_upload_url_ul.py b/tests/seahub/views/ajax/test_get_file_upload_url_ul.py index 5f81777cec..d92b04e92f 100644 --- a/tests/seahub/views/ajax/test_get_file_upload_url_ul.py +++ b/tests/seahub/views/ajax/test_get_file_upload_url_ul.py @@ -28,7 +28,7 @@ class GetFileUploadUrlULTest(BaseTestCase): resp = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') mock_get_fileserver_access_token.assert_called_with( self.repo.id, '{"anonymous_user": "%s"}' % self.user.username, - 'upload', '', use_onetime=False) + 'upload-link', '', use_onetime=False) json_resp = json.loads(resp.content) assert 'test_token' in json_resp['url'] @@ -40,7 +40,7 @@ class GetFileUploadUrlULTest(BaseTestCase): resp = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') mock_get_fileserver_access_token.assert_called_with( self.repo.id, '{"anonymous_user": ""}', - 'upload', '', use_onetime=False) + 'upload-link', '', use_onetime=False) json_resp = json.loads(resp.content) assert 'test_token' in json_resp['url'] @@ -55,7 +55,7 @@ class GetFileUploadUrlULTest(BaseTestCase): resp = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') mock_get_fileserver_access_token.assert_called_with( self.repo.id, '{"anonymous_user": "anonymous@email.com"}', - 'upload', '', use_onetime=False) + 'upload-link', '', use_onetime=False) json_resp = json.loads(resp.content) assert 'test_token' in json_resp['url'] @@ -70,6 +70,6 @@ class GetFileUploadUrlULTest(BaseTestCase): resp = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') mock_get_fileserver_access_token.assert_called_with( self.repo.id, '{"anonymous_user": ""}', - 'upload', '', use_onetime=False, check_virus=True) + 'upload-link', '', use_onetime=False, check_virus=True) json_resp = json.loads(resp.content) assert 'test_token' in json_resp['url'] From 122254905d43739472570de3ea7b69cc60222e21 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 14 Sep 2018 12:05:46 +0800 Subject: [PATCH 02/26] Share link auth (#2358) * rm view_raw_shared_file * add SHARE_LINK_LOGIN_REQUIRE setting to force user login when view preview file/folder share link --- seahub/settings.py | 3 + seahub/urls.py | 5 +- seahub/utils/__init__.py | 3 +- seahub/views/file.py | 91 ++++--------------- seahub/views/repo.py | 10 +- .../views/file/test_view_raw_shared_file.py | 88 ------------------ 6 files changed, 33 insertions(+), 167 deletions(-) delete mode 100644 tests/seahub/views/file/test_view_raw_shared_file.py diff --git a/seahub/settings.py b/seahub/settings.py index e6c53d670a..ccfb3d8763 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -312,6 +312,9 @@ REPO_PASSWORD_MIN_LENGTH = 8 # token length for the share link SHARE_LINK_TOKEN_LENGTH = 20 +# if limit only authenticated user can view preview share link +SHARE_LINK_LOGIN_REQUIRED = False + # min/max expire days for a share link SHARE_LINK_EXPIRE_DAYS_MIN = 0 # 0 means no limit SHARE_LINK_EXPIRE_DAYS_MAX = 0 # 0 means no limit diff --git a/seahub/urls.py b/seahub/urls.py index 4bfd078ea6..bda7713c32 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -11,8 +11,8 @@ from seahub.views.sso import * from seahub.views.file import view_history_file, view_trash_file,\ view_snapshot_file, file_edit, view_shared_file, view_file_via_shared_dir,\ - text_diff, view_raw_file, view_raw_shared_file, \ - download_file, view_lib_file, file_access, view_lib_file_via_smart_link + text_diff, view_raw_file, download_file, view_lib_file, \ + file_access, view_lib_file_via_smart_link from seahub.views.repo import repo_history_view, view_shared_dir, \ view_shared_upload_link from notifications.views import notification_list @@ -167,7 +167,6 @@ urlpatterns = [ ### share/upload link ### url(r'^f/(?P[a-f0-9]+)/$', view_shared_file, name='view_shared_file'), - url(r'^f/(?P[a-f0-9]+)/raw/(?P[0-9a-f]{40})/(?P.*)', view_raw_shared_file, name='view_raw_shared_file'), url(r'^d/(?P[a-f0-9]+)/$', view_shared_dir, name='view_shared_dir'), url(r'^d/(?P[a-f0-9]+)/files/$', view_file_via_shared_dir, name='view_file_via_shared_dir'), url(r'^u/d/(?P[a-f0-9]+)/$', view_shared_upload_link, name='view_shared_upload_link'), diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 614d58404d..9b673c6b31 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -31,6 +31,7 @@ from django.utils.http import urlquote from django.utils.html import escape from django.views.static import serve as django_static_serve +from seahub.auth import REDIRECT_FIELD_NAME from seahub.api2.models import Token, TokenV2 import seahub.settings from seahub.settings import SITE_NAME, MEDIA_URL, LOGO_PATH, \ @@ -920,7 +921,7 @@ def redirect_to_login(request): from django.conf import settings login_url = settings.LOGIN_URL path = urlquote(request.get_full_path()) - tup = login_url, redirect_field_name, path + tup = login_url, REDIRECT_FIELD_NAME, path return HttpResponseRedirect('%s?%s=%s' % tup) def mkstemp(): diff --git a/seahub/views/file.py b/seahub/views/file.py index 8e87a46c78..f832e3d74d 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -37,7 +37,6 @@ from seaserv import get_repo, send_message, get_commits, \ seafserv_threaded_rpc from pysearpc import SearpcError -from seahub.auth import REDIRECT_FIELD_NAME from seahub.tags.models import FileUUIDMap from seahub.wopi.utils import get_wopi_dict from seahub.onlyoffice.utils import get_onlyoffice_dict @@ -55,7 +54,7 @@ from seahub.utils import render_error, is_org_context, \ user_traffic_over_limit, get_file_audit_events_by_path, \ generate_file_audit_event_type, FILE_AUDIT_ENABLED, \ get_conf_text_ext, HAS_OFFICE_CONVERTER, FILEEXT_TYPE_MAP, \ - normalize_file_path, get_service_url + normalize_file_path, get_service_url, redirect_to_login from seahub.utils.ip import get_remote_ip from seahub.utils.timeutils import utc_to_local @@ -81,7 +80,8 @@ if HAS_OFFICE_CONVERTER: import seahub.settings as settings from seahub.settings import FILE_ENCODING_LIST, FILE_PREVIEW_MAX_SIZE, \ - FILE_ENCODING_TRY_LIST, MEDIA_URL, SEAFILE_COLLAB_SERVER, ENABLE_WATERMARK + FILE_ENCODING_TRY_LIST, MEDIA_URL, SEAFILE_COLLAB_SERVER, ENABLE_WATERMARK, \ + SHARE_LINK_LOGIN_REQUIRED try: from seahub.settings import ENABLE_OFFICE_WEB_APP @@ -928,6 +928,15 @@ def view_shared_file(request, fileshare): Download share file if `dl` in request param. View raw share file if `raw` in request param. """ + + # get share link permission + can_download = fileshare.get_permissions()['can_download'] + can_edit = fileshare.get_permissions()['can_edit'] + + if not request.user.is_authenticated(): + if SHARE_LINK_LOGIN_REQUIRED or can_edit: + return redirect_to_login(request) + token = fileshare.token # check if share link is encrypted @@ -952,16 +961,6 @@ def view_shared_file(request, fileshare): if not seafile_api.check_permission_by_path(repo_id, '/', shared_by): return render_error(request, _(u'Permission denied')) - # get share link permission - can_download = fileshare.get_permissions()['can_download'] - can_edit = fileshare.get_permissions()['can_edit'] - - if can_edit and not request.user.is_authenticated(): - login_url = settings.LOGIN_URL - path = urlquote(request.get_full_path()) - tup = login_url, REDIRECT_FIELD_NAME, path - return HttpResponseRedirect('%s?%s=%s' % tup) - # Increase file shared link view_cnt, this operation should be atomic fileshare.view_cnt = F('view_cnt') + 1 fileshare.save() @@ -1082,68 +1081,14 @@ def view_shared_file(request, fileshare): 'enable_watermark': ENABLE_WATERMARK, }) -def view_raw_shared_file(request, token, obj_id, file_name): - """Returns raw content of a shared file. - - Arguments: - - `request`: - - `token`: - - `obj_id`: - - `file_name`: - """ - fileshare = FileShare.objects.get_valid_file_link_by_token(token) - if fileshare is None: - raise Http404 - - password_check_passed, err_msg = check_share_link_common(request, fileshare) - if not password_check_passed: - d = {'token': token, 'err_msg': err_msg} - if fileshare.is_file_share_link(): - d['view_name'] = 'view_shared_file' - else: - d['view_name'] = 'view_shared_dir' - - return render(request, 'share_access_validation.html', d) - - repo_id = fileshare.repo_id - repo = get_repo(repo_id) - if not repo: - raise Http404 - - # Normalize file path based on file or dir share link - req_path = request.GET.get('p', '').rstrip('/') - if req_path: - file_path = posixpath.join(fileshare.path, req_path.lstrip('/')) - else: - if fileshare.is_file_share_link(): - file_path = fileshare.path.rstrip('/') - else: - file_path = fileshare.path.rstrip('/') + '/' + file_name - - real_obj_id = seafile_api.get_file_id_by_path(repo_id, file_path) - if not real_obj_id: - raise Http404 - - if real_obj_id != obj_id: # perm check - raise Http404 - - if not seafile_api.check_permission_by_path(repo_id, '/', - fileshare.username): - return render_error(request, _(u'Permission denied')) - - filename = os.path.basename(file_path) - username = request.user.username - token = seafile_api.get_fileserver_access_token(repo_id, - real_obj_id, 'view', username, use_onetime=False) - - if not token: - raise Http404 - - outer_url = gen_file_get_url(token, filename) - return HttpResponseRedirect(outer_url) - @share_link_audit def view_file_via_shared_dir(request, fileshare): + + # no edit permission for folder share link + if not request.user.is_authenticated() \ + and SHARE_LINK_LOGIN_REQUIRED: + return redirect_to_login(request) + token = fileshare.token # argument check diff --git a/seahub/views/repo.py b/seahub/views/repo.py index c47db7dd28..b9540ab8c4 100644 --- a/seahub/views/repo.py +++ b/seahub/views/repo.py @@ -24,11 +24,11 @@ from seahub.views import gen_path_link, get_repo_dirents, \ from seahub.utils import gen_dir_share_link, \ gen_shared_upload_link, user_traffic_over_limit, render_error, \ - get_file_type_and_ext + get_file_type_and_ext, redirect_to_login from seahub.settings import ENABLE_UPLOAD_FOLDER, \ ENABLE_RESUMABLE_FILEUPLOAD, ENABLE_THUMBNAIL, \ THUMBNAIL_ROOT, THUMBNAIL_DEFAULT_SIZE, THUMBNAIL_SIZE_FOR_GRID, \ - MAX_NUMBER_OF_FILES_FOR_FILEUPLOAD + MAX_NUMBER_OF_FILES_FOR_FILEUPLOAD, SHARE_LINK_LOGIN_REQUIRED from seahub.utils.file_types import IMAGE, VIDEO from seahub.thumbnail.utils import get_share_link_thumbnail_src from seahub.constants import HASH_URLS @@ -155,6 +155,12 @@ def repo_history_view(request, repo_id): ########## shared dir/uploadlink @share_link_audit def view_shared_dir(request, fileshare): + + # no edit permission for folder share link + if not request.user.is_authenticated() \ + and SHARE_LINK_LOGIN_REQUIRED: + return redirect_to_login(request) + token = fileshare.token password_check_passed, err_msg = check_share_link_common(request, fileshare) diff --git a/tests/seahub/views/file/test_view_raw_shared_file.py b/tests/seahub/views/file/test_view_raw_shared_file.py deleted file mode 100644 index 4b4a796cc4..0000000000 --- a/tests/seahub/views/file/test_view_raw_shared_file.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -from django.core.urlresolvers import reverse -from django.test import TestCase - -from seaserv import seafile_api - -from seahub.share.models import FileShare -from seahub.test_utils import Fixtures - -class RawSharedFileTest(TestCase, Fixtures): - def setUp(self): - share_file_info = { - 'username': 'test@test.com', - 'repo_id': self.repo.id, - 'path': '/', - 'password': None, - 'expire_date': None, - } - - self.fs = FileShare.objects.create_dir_link(**share_file_info) - self.file_id = seafile_api.get_file_id_by_path(self.repo.id, self.file) - self.filename= os.path.basename(self.file) - - def tearDown(self): - self.remove_repo() - - def test_can_get_fileserver_url(self): - resp = self.client.get( - reverse('view_raw_shared_file', args=[self.fs.token, - self.file_id, self.filename]) - ) - - self.assertEqual(302, resp.status_code) - self.assertRegexpMatches(resp['Location'], - r'http(.*)/files/[-0-9a-f]{36}/%s' % self.filename) - -class EncryptRawSharedFileTest(TestCase, Fixtures): - def setUp(self): - share_file_info = { - 'username': 'test@test.com', - 'repo_id': self.repo.id, - 'path': '/', - 'password': '12345678', - 'expire_date': None, - } - - self.fs = FileShare.objects.create_dir_link(**share_file_info) - self.file_id = seafile_api.get_file_id_by_path(self.repo.id, self.file) - self.filename= os.path.basename(self.file) - - def tearDown(self): - self.remove_repo() - - def test_can_decrypt(self): - resp = self.client.post( - reverse('view_raw_shared_file', args=[self.fs.token, - self.file_id, self.filename]), {'password': '12345678'} - ) - self.assertEqual(302, resp.status_code) - self.assertRegexpMatches(resp['Location'], - r'http(.*)/files/[-0-9a-f]{36}/%s' % self.filename) - - def test_wrong_password(self): - resp = self.client.post( - reverse('view_raw_shared_file', args=[self.fs.token, - self.file_id, self.filename]), {'password': '1234567'} - ) - - self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'share_access_validation.html') - self.assertContains(resp, 'Please enter a correct password') - - def test_no_password(self): - resp = self.client.get( - reverse('view_raw_shared_file', args=[self.fs.token, - self.file_id, self.filename]) - ) - - self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'share_access_validation.html') - - resp = self.client.post( - reverse('view_raw_shared_file', args=[self.fs.token, - self.file_id, self.filename]) - ) - - self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'share_access_validation.html') From c1d8b9024be4636c26eae356d648c94facd6b3f3 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 14 Sep 2018 18:13:25 +0800 Subject: [PATCH 03/26] Preview edit file (#2369) * Revert "update share link permission (#2328)" This reverts commit 6313e54dd75a74e347ad1c81293633dab11522dd. * update share link related UI Download link(s) to Share link(s) * update file api add 'can_preview' and 'can_edit' fields * update view file page 'invalid extension' to 'File preview unsupported' * update share popup 1. send file detail request to check if can preview/edit file 2. show or hide share link permission according to api response 3. add tests for can_preview/edit_file func * use FILEEXT_TYPE_MAP to check if can preview file --- seahub/api2/endpoints/file.py | 13 +- seahub/settings.py | 1 - seahub/templates/js/templates.html | 8 +- .../templates/snippets/file_content_html.html | 8 +- seahub/templates/view_file_base.html | 2 +- seahub/utils/__init__.py | 1 + seahub/views/ajax.py | 5 - seahub/views/file.py | 129 +++++++---- static/scripts/app/views/dirent-grid.js | 2 - static/scripts/app/views/dirent.js | 2 - static/scripts/app/views/share.js | 50 ++-- static/scripts/common.js | 1 + tests/seahub/views/file/test_can_edit_file.py | 216 ++++++++++++++++++ .../views/file/test_can_preview_file.py | 203 ++++++++++++++++ tests/seahub/views/file/test_view_lib_file.py | 2 +- 15 files changed, 549 insertions(+), 94 deletions(-) create mode 100644 tests/seahub/views/file/test_can_edit_file.py create mode 100644 tests/seahub/views/file/test_can_preview_file.py diff --git a/seahub/api2/endpoints/file.py b/seahub/api2/endpoints/file.py index 04ca56f95d..3adf9c3293 100644 --- a/seahub/api2/endpoints/file.py +++ b/seahub/api2/endpoints/file.py @@ -22,6 +22,7 @@ from seahub.utils import check_filename_with_rename, is_pro_version, \ from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.views import check_folder_permission from seahub.utils.file_op import check_file_lock, if_locked_by_online_office +from seahub.views.file import can_preview_file, can_edit_file from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \ FILE_LOCK_EXPIRATION_DAYS, OFFICE_TEMPLATE_ROOT @@ -44,7 +45,13 @@ class FileView(APIView): def get_file_info(self, username, repo_id, file_path): + repo = seafile_api.get_repo(repo_id) file_obj = seafile_api.get_dirent_by_path(repo_id, file_path) + file_name = file_obj.obj_name + file_size = file_obj.size + + can_preview, error_msg = can_preview_file(file_name, file_size, repo) + can_edit, error_msg = can_edit_file(file_name, file_size, repo) try: is_locked, locked_by_me = check_file_lock(repo_id, file_path, username) @@ -56,11 +63,13 @@ class FileView(APIView): 'type': 'file', 'repo_id': repo_id, 'parent_dir': os.path.dirname(file_path), - 'obj_name': file_obj.obj_name, + 'obj_name': file_name, 'obj_id': file_obj.obj_id, - 'size': file_obj.size, + 'size': file_size, 'mtime': timestamp_to_isoformat_timestr(file_obj.mtime), 'is_locked': is_locked, + 'can_preview': can_preview, + 'can_edit': can_edit, } return file_info diff --git a/seahub/settings.py b/seahub/settings.py index ccfb3d8763..9cdd733d8d 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -380,7 +380,6 @@ ENABLE_FILE_COMMENT = True # File preview FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024 -OFFICE_PREVIEW_MAX_SIZE = 2 * 1024 * 1024 FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5'] FILE_ENCODING_TRY_LIST = ['utf-8', 'gbk'] HIGHLIGHT_KEYWORD = False # If True, highlight the keywords in the file when the visit is via clicking a link in 'search result' page. diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html index 1dbd31e639..0a3ddf1344 100644 --- a/seahub/templates/js/templates.html +++ b/seahub/templates/js/templates.html @@ -1240,18 +1240,14 @@ {% trans "Preview and download" %}
- <% if (show_link_preview_only_perm_option) { %> -