def file_is_locked(request): token = request.GET.get('access_token', None) info_dict = get_file_info_by_token(token) repo_id = info_dict['repo_id'] file_path = info_dict['file_path'] return if_locked_by_online_office(repo_id, file_path)
def put(self, request, token): """ This api only used for refresh OnlineOffice lock when user edit office file via share link. Permission checking: 1, If enable SHARE_LINK_LOGIN_REQUIRED, user must have been authenticated. 2, Share link should have can_edit permission. 3, File must have been locked by OnlineOffice. """ if SHARE_LINK_LOGIN_REQUIRED and \ not request.user.is_authenticated(): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) try: share_link = FileShare.objects.get(token=token) except FileShare.DoesNotExist: error_msg = 'Share link %s not found.' % token return api_error(status.HTTP_404_NOT_FOUND, error_msg) if share_link.is_expired(): error_msg = 'Share link %s is expired.' % token return api_error(status.HTTP_400_BAD_REQUEST, error_msg) shared_by = share_link.username repo_id = share_link.repo_id path = normalize_file_path(share_link.path) parent_dir = os.path.dirname(path) if seafile_api.check_permission_by_path( repo_id, parent_dir, shared_by) != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) permissions = share_link.get_permissions() can_edit = permissions['can_edit'] if not can_edit: error_msg = 'Share link %s has no edit permission.' % token return api_error(status.HTTP_403_FORBIDDEN, error_msg) locked_by_online_office = if_locked_by_online_office(repo_id, path) if locked_by_online_office: # refresh lock file try: seafile_api.refresh_file_lock(repo_id, path) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) else: error_msg = _("You can not refresh this file's lock.") return api_error(status.HTTP_403_FORBIDDEN, error_msg) return Response({'success': True})
def file_is_locked(request): if not is_pro_version(): key, value = generate_file_lock_key_value(request) return True if cache.get(key, '') else False token = request.GET.get('access_token', None) info_dict = get_file_info_by_token(token) repo_id = info_dict['repo_id'] file_path = info_dict['file_path'] return if_locked_by_online_office(repo_id, file_path)
def put(self, request, token): """ This api only used for refresh OnlineOffice lock when user edit office file via share link. Permission checking: 1, If enable SHARE_LINK_LOGIN_REQUIRED, user must have been authenticated. 2, Share link should have can_edit permission. 3, File must have been locked by OnlineOffice. """ if SHARE_LINK_LOGIN_REQUIRED and \ not request.user.is_authenticated(): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) try: share_link = FileShare.objects.get(token=token) except FileShare.DoesNotExist: error_msg = 'Share link %s not found.' % token return api_error(status.HTTP_404_NOT_FOUND, error_msg) if share_link.is_expired(): error_msg = 'Share link %s is expired.' % token return api_error(status.HTTP_400_BAD_REQUEST, error_msg) shared_by = share_link.username repo_id = share_link.repo_id path = normalize_file_path(share_link.path) parent_dir = os.path.dirname(path) if seafile_api.check_permission_by_path(repo_id, parent_dir, shared_by) != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) permissions = share_link.get_permissions() can_edit = permissions['can_edit'] if not can_edit: error_msg = 'Share link %s has no edit permission.' % token return api_error(status.HTTP_403_FORBIDDEN, error_msg) locked_by_online_office = if_locked_by_online_office(repo_id, path) if locked_by_online_office: # refresh lock file try: seafile_api.refresh_file_lock(repo_id, path) except SearpcError, e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
def get(self, request, file_id, format=None): """ WOPI endpoint for check file info """ token = request.GET.get('access_token', None) request_user, repo_id, file_path = get_file_info_by_token(token) repo = seafile_api.get_repo(repo_id) obj_id = seafile_api.get_file_id_by_path(repo_id, file_path) try: file_size = seafile_api.get_file_size(repo.store_id, repo.version, obj_id) except SearpcError as e: logger.error(e) return HttpResponse(json.dumps({}), status=500, content_type=json_content_type) if file_size == -1: logger.error('File %s not found.' % file_path) return HttpResponse(json.dumps({}), status=401, content_type=json_content_type) result = {} # necessary result['BaseFileName'] = os.path.basename(file_path) result['Size'] = file_size result['UserId'] = request_user result['Version'] = obj_id try: if is_pro_version(): result['OwnerId'] = seafile_api.get_repo_owner(repo_id) or \ seafile_api.get_org_repo_owner(repo_id) else: result['OwnerId'] = seafile_api.get_repo_owner(repo_id) except Exception as e: logger.error(e) return HttpResponse(json.dumps({}), status=500, content_type=json_content_type) # optional result['UserFriendlyName'] = email2nickname(request_user) absolute_uri = request.build_absolute_uri('/') result['PostMessageOrigin'] = urlparse.urljoin(absolute_uri, SITE_ROOT).strip('/') result['HidePrintOption'] = False result['HideSaveOption'] = False result['HideExportOption'] = False result['EnableOwnerTermination'] = True result['SupportsLocks'] = True result['SupportsGetLock'] = True result['SupportsUpdate'] = True filename = os.path.basename(file_path) filetype, fileext = get_file_type_and_ext(filename) try: is_locked, locked_by_me = check_file_lock(repo_id, file_path, request_user) except Exception as e: logger.error(e) return HttpResponse(json.dumps({}), status=500, content_type=json_content_type) locked_by_online_office = if_locked_by_online_office( repo_id, file_path) perm = seafile_api.check_permission_by_path(repo_id, file_path, request_user) if ENABLE_OFFICE_WEB_APP_EDIT and not repo.encrypted and \ perm == 'rw' and fileext in OFFICE_WEB_APP_EDIT_FILE_EXTENSION and \ ((not is_locked) or (is_locked and locked_by_me) or \ (is_locked and locked_by_online_office)): result['UserCanWrite'] = True return HttpResponse(json.dumps(result), status=200, content_type=json_content_type)
def onlyoffice_editor_callback(request): """ Callback func of OnlyOffice. The document editing service informs the document storage service about status of the document editing using the callbackUrl from JavaScript API. The document editing service use the POST request with the information in body. https://api.onlyoffice.com/editors/callback """ if request.method != 'POST': logger.error('Request method if not POST.') # The document storage service must return the following response. # otherwise the document editor will display an error message. return HttpResponse('{"error": 0}') # body info of POST rquest when open file on browser # {u'actions': [{u'type': 1, u'userid': u'uid-1527736776860'}], # u'key': u'8062bdccf9b4cf809ae3', # u'status': 1, # u'users': [u'uid-1527736776860']} # body info of POST rquest when close file's web page (save file) # {u'actions': [{u'type': 0, u'userid': u'uid-1527736951523'}], # u'changesurl': u'...', # u'history': {u'changes': [{u'created': u'2018-05-31 03:17:17', # u'user': {u'id': u'uid-1527736577058', # u'name': u'lian'}}, # {u'created': u'2018-05-31 03:23:55', # u'user': {u'id': u'uid-1527736951523', # u'name': u'lian'}}], # u'serverVersion': u'5.1.4'}, # u'key': u'61484dec693009f3d506', # u'lastsave': u'2018-05-31T03:23:55.767Z', # u'notmodified': False, # u'status': 2, # u'url': u'...', # u'users': [u'uid-1527736951523']} # Defines the status of the document. Can have the following values: # 0 - no document with the key identifier could be found, # 1 - document is being edited, # 2 - document is ready for saving, # 3 - document saving error has occurred, # 4 - document is closed with no changes, # 6 - document is being edited, but the current document state is saved, # 7 - error has occurred while force saving the document. # Status 1 is received every user connection to or disconnection from document co-editing. # # Status 2 (3) is received 10 seconds after the document is closed for editing with the identifier of the user who was the last to send the changes to the document editing service. # # Status 4 is received after the document is closed for editing with no changes by the last user. # # Status 6 (7) is received when the force saving request is performed. post_data = json.loads(request.body) status = int(post_data.get('status', -1)) if status not in (1, 2, 4, 6): logger.error('onlyoffice status invalid: {}'.format(status)) return HttpResponse('{"error": 0}') # get file basic info doc_key = post_data.get('key') doc_info = json.loads(cache.get("ONLYOFFICE_%s" % doc_key)) repo_id = doc_info['repo_id'] file_path = doc_info['file_path'] username = doc_info['username'] cache_key = generate_onlyoffice_cache_key(repo_id, file_path) # save file if status in (2, 6): # Defines the link to the edited document to be saved with the document storage service. # The link is present when the status value is equal to 2 or 3 only. url = post_data.get('url') onlyoffice_resp = requests.get(url, verify=VERIFY_ONLYOFFICE_CERTIFICATE) if not onlyoffice_resp: logger.error('[OnlyOffice] No response from file content url.') return HttpResponse('{"error": 0}') fake_obj_id = {'online_office_update': True} update_token = seafile_api.get_fileserver_access_token( repo_id, json.dumps(fake_obj_id), 'update', username) if not update_token: logger.error('[OnlyOffice] No fileserver access token.') return HttpResponse('{"error": 0}') # get file content files = { 'file': onlyoffice_resp.content, 'file_name': os.path.basename(file_path), 'target_file': file_path, } # update file update_url = gen_inner_file_upload_url('update-api', update_token) requests.post(update_url, files=files) # 2 - document is ready for saving, if status == 2: cache.delete(cache_key) cache.delete("ONLYOFFICE_%s" % doc_key) if is_pro_version() and if_locked_by_online_office( repo_id, file_path): seafile_api.unlock_file(repo_id, file_path) # 6 - document is being edited, but the current document state is saved, if status == 6: # cache document key when forcesave cache.set(cache_key, doc_key) # 4 - document is closed with no changes, if status == 4: cache.delete(cache_key) cache.delete("ONLYOFFICE_%s" % doc_key) if is_pro_version() and if_locked_by_online_office(repo_id, file_path): seafile_api.unlock_file(repo_id, file_path) return HttpResponse('{"error": 0}')
def put(self, request, repo_id, format=None): """ Currently only support lock, unlock, refresh-lock file. Permission checking: 1. user with 'rw' permission for current file; """ if not is_pro_version(): error_msg = 'file lock feature only supported in professional edition.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) # argument check path = request.GET.get('p', None) if not path: error_msg = 'p invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) path = normalize_file_path(path) operation = request.data.get('operation', None) if not operation: error_msg = 'operation invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = operation.lower() if operation not in ('lock', 'unlock', 'refresh-lock'): error_msg = "operation can only be 'lock', 'unlock' or 'refresh-lock'." return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check 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) file_id = seafile_api.get_file_id_by_path(repo_id, path) if not file_id: error_msg = 'File %s not found.' % path return api_error(status.HTTP_404_NOT_FOUND, error_msg) # permission check parent_dir = os.path.dirname(path) if check_folder_permission(request, repo_id, parent_dir) != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) username = request.user.username try: is_locked, locked_by_me = check_file_lock(repo_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) # check if is locked by online office locked_by_online_office = if_locked_by_online_office(repo_id, path) if operation == 'lock': if is_locked: error_msg = _("File is locked") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # lock file expire = request.data.get('expire', FILE_LOCK_EXPIRATION_DAYS) try: seafile_api.lock_file(repo_id, path, username, expire) except SearpcError, e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
def put(self, request, repo_id, format=None): """ Currently only support lock, unlock, refresh-lock file. Permission checking: 1. user with 'rw' permission for current file; """ if not is_pro_version(): error_msg = 'file lock feature only supported in professional edition.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) # argument check path = request.GET.get('p', None) if not path: error_msg = 'p invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) path = normalize_file_path(path) operation = request.data.get('operation', None) if not operation: error_msg = 'operation invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = operation.lower() if operation not in ('lock', 'unlock', 'refresh-lock'): error_msg = "operation can only be 'lock', 'unlock' or 'refresh-lock'." return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check 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) file_id = seafile_api.get_file_id_by_path(repo_id, path) if not file_id: error_msg = 'File %s not found.' % path return api_error(status.HTTP_404_NOT_FOUND, error_msg) # permission check parent_dir = os.path.dirname(path) if check_folder_permission(request, repo_id, parent_dir) != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) username = request.user.username try: is_locked, locked_by_me = check_file_lock(repo_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) # check if is locked by online office locked_by_online_office = if_locked_by_online_office(repo_id, path) if operation == 'lock': if is_locked: error_msg = _("File is locked") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) expire = request.data.get('expire', 0) try: expire = int(expire) except ValueError: error_msg = 'expire invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) if expire < 0: error_msg = 'expire invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # lock file try: if expire > 0: seafile_api.lock_file(repo_id, path, username, int(time.time()) + expire) else: seafile_api.lock_file(repo_id, path, username, 0) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) if operation == 'unlock': if not is_locked: error_msg = _("File is not locked.") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) if locked_by_me or locked_by_online_office or \ is_repo_owner(request, repo_id, username) or \ is_repo_admin(username, repo_id): # unlock file try: seafile_api.unlock_file(repo_id, path) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) else: error_msg = 'You can not unlock this file.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) if operation == 'refresh-lock': if not is_locked: error_msg = _("File is not locked.") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) expire = request.data.get('expire', 0) try: expire = int(expire) except ValueError: error_msg = 'expire invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) if expire < 0: error_msg = 'expire invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) if locked_by_me or locked_by_online_office: # refresh lock file try: if expire > 0: seafile_api.refresh_file_lock( repo_id, path, int(time.time()) + expire) else: seafile_api.refresh_file_lock(repo_id, path) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) else: error_msg = _("You can not refresh this file's lock.") return api_error(status.HTTP_403_FORBIDDEN, error_msg) file_info = self.get_file_info(username, repo_id, path) return Response(file_info)
def get_onlyoffice_dict(request, username, repo_id, file_path, file_id='', can_edit=False, can_download=True): logger.info('{} open file {} in repo {} with can_edit {}'.format( username, file_path, repo_id, can_edit)) repo = seafile_api.get_repo(repo_id) if repo.is_virtual: origin_repo_id = repo.origin_repo_id origin_file_path = posixpath.join(repo.origin_path, file_path.strip('/')) # for view history/trash/snapshot file if not file_id: file_id = seafile_api.get_file_id_by_path(origin_repo_id, origin_file_path) else: origin_repo_id = repo_id origin_file_path = file_path if not file_id: file_id = seafile_api.get_file_id_by_path(repo_id, file_path) dl_token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', username, use_onetime=False) if not dl_token: return None filetype, fileext = get_file_type_and_ext(file_path) if fileext in ('xls', 'xlsx', 'ods', 'fods', 'csv'): document_type = 'spreadsheet' elif fileext in ('pptx', 'ppt', 'odp', 'fodp', 'ppsx', 'pps'): document_type = 'presentation' else: document_type = 'text' if not can_edit: info_bytes = force_bytes(origin_repo_id + origin_file_path + file_id) doc_key = hashlib.md5(info_bytes).hexdigest()[:20] else: cache_key = generate_onlyoffice_cache_key(origin_repo_id, origin_file_path) doc_key = cache.get(cache_key) # temporary solution when failed to get data from cache(django_pylibmc) # when init process for the first time if not doc_key: doc_key = cache.get(cache_key) if doc_key: logger.info('get doc_key {} from cache by cache_key {}'.format( doc_key, cache_key)) else: # In theory, file is unlocked when editing finished. # This can happend if memcache is restarted or memcache is full and doc key is deleted. if if_locked_by_online_office(repo_id, file_path): logger.warning( 'no doc_key in cache, but file {} in {} is locked by online office' .format(file_path, repo_id)) # generate doc_key info_bytes = force_bytes(origin_repo_id + origin_file_path + file_id) doc_key = hashlib.md5(info_bytes).hexdigest()[:20] logger.info( 'generate new doc_key {} by repo_id {} file_path {} file_id {}' .format(doc_key, origin_repo_id, origin_file_path, file_id)) logger.info('set cache_key {} and doc_key {} to cache'.format( cache_key, doc_key)) cache.set(cache_key, doc_key, None) if not cache.get("ONLYOFFICE_%s" % doc_key): doc_info = json.dumps({ 'repo_id': origin_repo_id, 'file_path': origin_file_path, 'username': username }) cache.set("ONLYOFFICE_%s" % doc_key, doc_info, None) logger.info('set doc_key {} and doc_info {} to cache'.format( doc_key, doc_info)) # for render onlyoffice html file_name = os.path.basename(file_path.rstrip('/')) doc_url = gen_file_get_url(dl_token, file_name) base_url = get_site_scheme_and_netloc() onlyoffice_editor_callback_url = reverse('onlyoffice_editor_callback') callback_url = urllib.parse.urljoin(base_url, onlyoffice_editor_callback_url) return_dict = { 'repo_id': repo_id, 'path': file_path, 'ONLYOFFICE_APIJS_URL': ONLYOFFICE_APIJS_URL, 'file_type': fileext, 'doc_key': doc_key, 'doc_title': file_name, 'doc_url': doc_url, 'document_type': document_type, 'callback_url': callback_url, 'can_edit': can_edit, 'can_download': can_download, 'username': username, 'onlyoffice_force_save': ONLYOFFICE_FORCE_SAVE, 'enable_watermark': ENABLE_WATERMARK, } if ONLYOFFICE_JWT_SECRET: import jwt config = { "document": { "fileType": fileext, "key": doc_key, "title": file_name, "url": doc_url, "permissions": { "download": can_download, "edit": can_edit, "print": can_download, "review": True } }, "documentType": document_type, "editorConfig": { "callbackUrl": callback_url, "lang": request.LANGUAGE_CODE, "mode": can_edit, "customization": { "forcesave": ONLYOFFICE_FORCE_SAVE, }, "user": { "name": email2nickname(username) } } } return_dict['onlyoffice_jwt_token'] = jwt.encode( config, ONLYOFFICE_JWT_SECRET) return return_dict
def put(self, request, repo_id, format=None): """ Currently only support lock, unlock, refresh-lock file. Permission checking: 1. user with 'rw' permission for current file; """ if not is_pro_version(): error_msg = 'file lock feature only supported in professional edition.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) # argument check path = request.GET.get('p', None) if not path: error_msg = 'p invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) path = normalize_file_path(path) operation = request.data.get('operation', None) if not operation: error_msg = 'operation invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) operation = operation.lower() if operation not in ('lock', 'unlock', 'refresh-lock'): error_msg = "operation can only be 'lock', 'unlock' or 'refresh-lock'." return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check 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) file_id = seafile_api.get_file_id_by_path(repo_id, path) if not file_id: error_msg = 'File %s not found.' % path return api_error(status.HTTP_404_NOT_FOUND, error_msg) # permission check parent_dir = os.path.dirname(path) if check_folder_permission(request, repo_id, parent_dir) != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) username = request.user.username try: is_locked, locked_by_me = check_file_lock(repo_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) # check if is locked by online office locked_by_online_office = if_locked_by_online_office(repo_id, path) if operation == 'lock': if is_locked: error_msg = _("File is locked") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # lock file expire = request.data.get('expire', FILE_LOCK_EXPIRATION_DAYS) try: seafile_api.lock_file(repo_id, path, username, expire) except SearpcError, e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)