From 122254905d43739472570de3ea7b69cc60222e21 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 14 Sep 2018 12:05:46 +0800 Subject: [PATCH] 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')