From 2f0fb01f021374d0afe8bea71f85eda464c1a672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=81=A5=E8=BE=89?= Date: Mon, 12 Aug 2019 10:32:57 +0800 Subject: [PATCH] dtable accessToken (#3953) * get dtable accessToken * add testcase * change token load method * repair code error * update dtable version --- frontend/package-lock.json | 32 ++++++------- frontend/package.json | 4 +- frontend/src/view-file-dtable.js | 2 +- seahub/api2/endpoints/dtable.py | 72 ++++++++++++++++++++++++++++-- seahub/settings.py | 7 +++ seahub/urls.py | 5 ++- tests/api/endpoints/test_dtable.py | 29 ++++++++++++ 7 files changed, 126 insertions(+), 25 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0d366ad047..4b27a7594d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -184,9 +184,9 @@ } }, "@seafile/dtable": { - "version": "0.0.108", - "resolved": "https://registry.npmjs.org/@seafile/dtable/-/dtable-0.0.108.tgz", - "integrity": "sha512-FwkJS35R0AUbu1HS/L35KIsuNtMVeIQLuDHxJe6O7IRj3nsQB6o/q3Od8Nkn24rt+PgOSlvFaoomPL4w8WCA6Q==", + "version": "0.0.110", + "resolved": "https://registry.npmjs.org/@seafile/dtable/-/dtable-0.0.110.tgz", + "integrity": "sha512-Ykz137zxop//spmTHu1kHiynBuUflWtAl9qRTqLHnOTOTsEkG3hWISIXwdVLJPpoPfqY0hYD5/RVQ2RE8vkUPA==", "requires": { "@babel/plugin-proposal-export-default-from": "^7.5.2", "@babel/plugin-proposal-export-namespace-from": "^7.5.2", @@ -216,7 +216,7 @@ "reactstrap": "^8.0.0", "reselect": "^4.0.0", "ron-react-autocomplete": "^4.0.9", - "seafile-js": "^0.2.111", + "seafile-js": "^0.2.112", "shallowequal": "^1.1.0", "socket.io-client": "^2.2.0", "uuid": "^3.3.2", @@ -481,9 +481,9 @@ } }, "electron-to-chromium": { - "version": "1.3.220", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.220.tgz", - "integrity": "sha512-ZsaFWi+9J9Nsm4OmGM/BvZF3HEeZL4bte1+CcN9vHUcqdkOOVAXP4SeacPZ/W5uCQZEKPYBXg6yUjZx8/jpD0Q==" + "version": "1.3.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.222.tgz", + "integrity": "sha512-Kv3rvtJELafNfgVBVNaDIdV0aWV7O1RlYqqAhg+s+OwpiXFYPsIvONYgAopmR/gpyxSYbHi0EKJmPOvaL7UzMg==" }, "esprima": { "version": "4.0.1", @@ -13570,9 +13570,9 @@ } }, "electron-to-chromium": { - "version": "1.3.220", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.220.tgz", - "integrity": "sha512-ZsaFWi+9J9Nsm4OmGM/BvZF3HEeZL4bte1+CcN9vHUcqdkOOVAXP4SeacPZ/W5uCQZEKPYBXg6yUjZx8/jpD0Q==" + "version": "1.3.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.222.tgz", + "integrity": "sha512-Kv3rvtJELafNfgVBVNaDIdV0aWV7O1RlYqqAhg+s+OwpiXFYPsIvONYgAopmR/gpyxSYbHi0EKJmPOvaL7UzMg==" }, "postcss": { "version": "7.0.17", @@ -13976,9 +13976,9 @@ } }, "electron-to-chromium": { - "version": "1.3.220", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.220.tgz", - "integrity": "sha512-ZsaFWi+9J9Nsm4OmGM/BvZF3HEeZL4bte1+CcN9vHUcqdkOOVAXP4SeacPZ/W5uCQZEKPYBXg6yUjZx8/jpD0Q==" + "version": "1.3.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.222.tgz", + "integrity": "sha512-Kv3rvtJELafNfgVBVNaDIdV0aWV7O1RlYqqAhg+s+OwpiXFYPsIvONYgAopmR/gpyxSYbHi0EKJmPOvaL7UzMg==" }, "postcss": { "version": "7.0.17", @@ -16274,9 +16274,9 @@ } }, "seafile-js": { - "version": "0.2.111", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.111.tgz", - "integrity": "sha512-PsaU3VUledLrs8guKRm0nJ99s0g3bgQmpvIb00b1ESjdsgRIcyvQsgCrG3fTJQYn+fMdGJaSP1tiDbuOZLoqrA==", + "version": "0.2.112", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.112.tgz", + "integrity": "sha512-l91Vpsu7BAeGL1a0oOT6Tj0hPMYXPRoQy1sd70bGb/wc//W6JqqUMJ9sW7Gkb39sJF8BzqVIECTVNIDzOZ/7OQ==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2", diff --git a/frontend/package.json b/frontend/package.json index 29e3b42a8c..31a8a83c69 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@reach/router": "^1.2.0", - "@seafile/dtable": "0.0.108", + "@seafile/dtable": "0.0.110", "@seafile/resumablejs": "^1.1.9", "@seafile/seafile-editor": "^0.2.57", "MD5": "^1.3.0", @@ -38,7 +38,7 @@ "react-responsive": "^6.1.2", "react-select": "^2.4.1", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.111", + "seafile-js": "^0.2.112", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", diff --git a/frontend/src/view-file-dtable.js b/frontend/src/view-file-dtable.js index 8cf14f538a..aa5c651e86 100644 --- a/frontend/src/view-file-dtable.js +++ b/frontend/src/view-file-dtable.js @@ -17,7 +17,7 @@ window.dtable = { filePath: filePath, fileName: fileName, dtableUuid: dtableUuid, - accessToken: '12345678' + accessToken: '' }; window.seafileAPI = seafileAPI; diff --git a/seahub/api2/endpoints/dtable.py b/seahub/api2/endpoints/dtable.py index 1b0aec3234..b219def8ae 100644 --- a/seahub/api2/endpoints/dtable.py +++ b/seahub/api2/endpoints/dtable.py @@ -2,6 +2,8 @@ import os import logging +import time +import jwt from rest_framework.views import APIView from rest_framework.authentication import SessionAuthentication @@ -23,7 +25,7 @@ from seahub.group.utils import group_id_to_name from seahub.utils import is_valid_dirent_name, is_org_context, normalize_file_path, \ check_filename_with_rename, gen_file_upload_url from seahub.views.file import send_file_access_msg -from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN +from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, DTABLE_PRIVATE_KEY from seahub.dtable.utils import check_dtable_share_permission, check_dtable_permission from seahub.constants import PERMISSION_ADMIN, PERMISSION_READ_WRITE @@ -434,7 +436,7 @@ class DTableView(APIView): parent_dir = os.path.dirname(asset_dir_path) file_name = os.path.basename(asset_dir_path) try: - seafile_api.del_file(repo_id, parent_dir, file_name, owner) + seafile_api.del_file(repo_id, parent_dir, file_name, username) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' @@ -442,7 +444,7 @@ class DTableView(APIView): # delete table try: - seafile_api.del_file(repo_id, '/', table_file_name, owner) + seafile_api.del_file(repo_id, '/', table_file_name, username) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' @@ -577,7 +579,7 @@ class DTableAssetUploadLinkView(APIView): asset_dir_path = '/asset/' + str(dtable.uuid) asset_dir_id = seafile_api.get_dir_id_by_path(repo_id, asset_dir_path) if not asset_dir_id: - seafile_api.mkdir_with_parents(repo_id, '/', asset_dir_path[1:], owner) + seafile_api.mkdir_with_parents(repo_id, '/', asset_dir_path[1:], username) dtable.modifier = username dtable.save() @@ -586,3 +588,65 @@ class DTableAssetUploadLinkView(APIView): res['upload_link'] = upload_link res['parent_path'] = asset_dir_path return Response(res) + + +class DTableAccessTokenView(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def get(self, request, workspace_id, name): + """get dtable access token + """ + + table_name = name + table_file_name = table_name + FILE_TYPE + + # resource check + workspace = Workspaces.objects.get_workspace_by_id(workspace_id) + if not workspace: + error_msg = 'Workspace %s not found.' % workspace_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + repo_id = workspace.repo_id + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + dtable = DTables.objects.get_dtable(workspace, table_name) + if not dtable: + error_msg = 'dtable %s not found.' % table_name + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + table_path = normalize_file_path(table_file_name) + table_file_id = seafile_api.get_file_id_by_path(repo_id, table_path) + if not table_file_id: + error_msg = 'file %s not found.' % table_file_name + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # permission check + username = request.user.username + owner = workspace.owner + if not check_dtable_permission(username, owner) and \ + not check_dtable_share_permission(dtable, username): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + # generate json web token + payload = { + 'exp': int(time.time()) + 86400 * 3, + 'dtable_uuid': dtable.uuid.hex, + } + + try: + access_token = jwt.encode( + payload, DTABLE_PRIVATE_KEY, algorithm='HS256' + ) + 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({'access_token': access_token}) diff --git a/seahub/settings.py b/seahub/settings.py index d53b80575d..93b31372f5 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -746,9 +746,16 @@ ENABLE_SUB_LIBRARY = True SEAFILE_COLLAB_SERVER = '' +########################## +# Settings for dtable # +########################## + # dtable server url DTABLE_SERVER_URL = '' +# dtable private key +DTABLE_PRIVATE_KEY = '' + ############################ # Settings for Seahub Priv # ############################ diff --git a/seahub/urls.py b/seahub/urls.py index 311d52c9cb..e31ccf1e95 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -91,7 +91,7 @@ from seahub.api2.endpoints.starred_items import StarredItems from seahub.api2.endpoints.markdown_lint import MarkdownLintView from seahub.api2.endpoints.public_repos_search import PublishedRepoSearchView from seahub.api2.endpoints.dtable import WorkspacesView, DTableView, DTablesView, \ - DTableUpdateLinkView, DTableAssetUploadLinkView + DTableUpdateLinkView, DTableAssetUploadLinkView, DTableAccessTokenView from seahub.api2.endpoints.dtable_share import SharedDTablesView, DTableShareView from seahub.api2.endpoints.dtable_related_users import DTableRelatedUsersView @@ -374,7 +374,7 @@ urlpatterns = [ # public repos search url(r'^api/v2.1/published-repo-search/$', PublishedRepoSearchView.as_view(), name='api-v2.1-published-repo-search'), - # user: workspaces + # user: dtable url(r'^api/v2.1/workspaces/$', WorkspacesView.as_view(), name='api-v2.1-workspaces'), url(r'^api/v2.1/dtables/$', DTablesView.as_view(), name='api-v2.1-dtables'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/$', DTableView.as_view(), name='api-v2.1-workspace-dtable'), @@ -383,6 +383,7 @@ urlpatterns = [ url(r'^api/v2.1/dtables/shared/$', SharedDTablesView.as_view(), name='api-v2.1-dtables-share'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/share/$', DTableShareView.as_view(), name='api-v2.1-dtable-share'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/related-users/$', DTableRelatedUsersView.as_view(), name='api-v2.1-dtable-related-users'), + url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/access-token/$', DTableAccessTokenView.as_view(), name='api-v2.1-dtable-access-token'), # Deprecated url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/tags/$', FileTagsView.as_view(), name="api-v2.1-filetags-view"), diff --git a/tests/api/endpoints/test_dtable.py b/tests/api/endpoints/test_dtable.py index e4767846b8..b35a14ec46 100644 --- a/tests/api/endpoints/test_dtable.py +++ b/tests/api/endpoints/test_dtable.py @@ -162,3 +162,32 @@ class DTableTest(BaseTestCase): data = 'name=%s' % 'table9' resp = self.client.delete(self.url2, data, 'application/x-www-form-urlencoded') self.assertEqual(403, resp.status_code) + + def test_can_get_access_token(self): + resp = self.client.post(self.url1, {'name': 'table10', 'owner': self.user.username}) + self.assertEqual(201, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp["table"]["name"] == 'table10' + + url = reverse('api-v2.1-dtable-access-token', args=[self.workspace.id, 'table10']) + resp = self.client.get(url, {}, 'application/x-www-form-urlencoded') + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + self.assertIsNotNone(json_resp["access_token"]) + + def test_get_access_token_with_invalid_repo(self): + resp = self.client.post(self.url1, {'name': 'table11', 'owner': self.user.username}) + self.assertEqual(201, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp["table"]["name"] == 'table11' + + url3 = reverse('api2-repo', args=[self.workspace.repo_id]) + resp = self.client.delete(url3, {}, 'application/x-www-form-urlencoded') + self.assertEqual(200, resp.status_code) + + url4 = reverse('api-v2.1-dtable-access-token', args=[self.workspace.id, 'table11']) + resp = self.client.get(url4, {}, 'application/x-www-form-urlencoded') + self.assertEqual(404, resp.status_code)