Merge remote-tracking branch 'remotes/origin/develop' into feature/referenceData-open

# Conflicts:
#	CHANGELOG.md
#	web/documentserver-example/python/src/views/actions.py
This commit is contained in:
Sergey Linnik
2023-10-31 14:25:00 +03:00
67 changed files with 889 additions and 780 deletions

View File

@ -20,15 +20,19 @@
from http import HTTPStatus, HTTPMethod
from django.http import HttpRequest, HttpResponse
def GET():
return method(HTTPMethod.GET)
def POST():
return method(HTTPMethod.POST)
def PUT():
return method(HTTPMethod.PUT)
def method(meth: HTTPMethod):
def wrapper(func):
def inner(request: HttpRequest, *args, **kwargs):

View File

@ -23,10 +23,12 @@ from . import http
# Under the hood, HttpRequest uses a settings object.
settings.configure()
@http.GET()
def endpoint(_: HttpRequest) -> HttpResponse:
return HttpResponse()
class HTTPMethodTests(TestCase):
def test_returns_a_response_from_the_endpoint(self):
request = HttpRequest()

View File

@ -16,6 +16,7 @@
from typing import Optional
def boolean(string: Optional[str], default: bool = False) -> bool:
'''
Converts a string that represents a boolean value to its corresponding

View File

@ -17,6 +17,7 @@
from unittest import TestCase
from .string import boolean
class BooleanDefaultTests(TestCase):
def test_converts_to_the_default_value(self):
value = boolean("unknown")
@ -30,6 +31,7 @@ class BooleanDefaultTests(TestCase):
value = boolean("unknown", True)
self.assertTrue(value)
class BooleanOptionalTests(TestCase):
def test_converts_to_the_default_value(self):
value = boolean(None)
@ -43,12 +45,14 @@ class BooleanOptionalTests(TestCase):
value = boolean(None, True)
self.assertTrue(value)
class BooleanNegativeTests(TestCase):
def test_converts_a_negative_string_to_the_negative_value(self):
for string in ["false", "f", "no", "n", "0"]:
value = boolean(string)
self.assertFalse(value)
class BooleanPositiveTests(TestCase):
def test_converts_a_positive_string_to_the_positive_value(self):
for string in ["true", "t", "yes", "y", "1"]:

View File

@ -20,6 +20,7 @@ from typing import Optional
from urllib.parse import ParseResult, urlparse, urljoin
from src.common import string
class ConfigurationManager:
version = '1.7.0'

View File

@ -20,11 +20,13 @@ from unittest.mock import patch
from urllib.parse import urlparse
from . import ConfigurationManager
class ConfigurationManagerTests(TestCase):
def test_corresponds_the_latest_version(self):
config_manager = ConfigurationManager()
self.assertEqual(config_manager.version, '1.6.0')
class ConfigurationManagerExampleURLTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -39,6 +41,7 @@ class ConfigurationManagerExampleURLTests(TestCase):
url = config_manager.example_url()
self.assertEqual(url.geturl(), 'http://localhost')
class ConfigurationManagerDocumentServerPublicURLTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -53,6 +56,7 @@ class ConfigurationManagerDocumentServerPublicURLTests(TestCase):
url = config_manager.document_server_public_url()
self.assertEqual(url.geturl(), 'http://localhost')
class ConfigurationManagerDocumentServerPrivateURLTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -67,6 +71,7 @@ class ConfigurationManagerDocumentServerPrivateURLTests(TestCase):
url = config_manager.document_server_private_url()
self.assertEqual(url.geturl(), 'http://localhost')
class ConfigurationManagerDocumentServerAPIURLTests(TestCase):
@patch.object(
ConfigurationManager,
@ -97,6 +102,7 @@ class ConfigurationManagerDocumentServerAPIURLTests(TestCase):
'http://localhost/api'
)
class ConfigurationManagerDocumentServerPreloaderURLTests(TestCase):
@patch.object(
ConfigurationManager,
@ -127,6 +133,7 @@ class ConfigurationManagerDocumentServerPreloaderURLTests(TestCase):
'http://localhost/preloader'
)
class ConfigurationManagerDocumentServerCommandURLTests(TestCase):
@patch.object(
ConfigurationManager,
@ -157,6 +164,7 @@ class ConfigurationManagerDocumentServerCommandURLTests(TestCase):
'http://localhost/command'
)
class ConfigurationManagerDocumentServerConverterURLTests(TestCase):
@patch.object(
ConfigurationManager,
@ -187,6 +195,7 @@ class ConfigurationManagerDocumentServerConverterURLTests(TestCase):
'http://localhost/converter'
)
class ConfigurationManagerJWTSecretTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -201,6 +210,7 @@ class ConfigurationManagerJWTSecretTests(TestCase):
secret = config_manager.jwt_secret()
self.assertEqual(secret, 'your-256-bit-secret')
class ConfigurationManagerJWTHeaderTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -215,6 +225,7 @@ class ConfigurationManagerJWTHeaderTests(TestCase):
header = config_manager.jwt_header()
self.assertEqual(header, 'Proxy-Authorization')
class ConfigurationManagerJWTUseForRequest(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -229,6 +240,7 @@ class ConfigurationManagerJWTUseForRequest(TestCase):
use = config_manager.jwt_use_for_request()
self.assertFalse(use)
class ConfigurationManagerSSLTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -243,6 +255,7 @@ class ConfigurationManagerSSLTests(TestCase):
enabled = config_manager.ssl_verify_peer_mode_enabled()
self.assertTrue(enabled)
class ConfigurationManagerStoragePathTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -267,6 +280,7 @@ class ConfigurationManagerStoragePathTests(TestCase):
path = config_manager.storage_path()
self.assertEqual(f'{path}', '/directory')
class ConfigurationManagerMaximumFileSizeTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()
@ -281,6 +295,7 @@ class ConfigurationManagerMaximumFileSizeTests(TestCase):
size = config_manager.maximum_file_size()
self.assertEqual(size, 10)
class ConfigurationManagerConversionTimeoutTests(TestCase):
def test_assigns_a_default_value(self):
config_manager = ConfigurationManager()

View File

@ -19,6 +19,7 @@ from msgspec.json import decode
from msgspec import Struct
from src.memoize import memoize
class Format(Struct):
name: str
type: str
@ -29,6 +30,7 @@ class Format(Struct):
def extension(self) -> str:
return f'.{self.name}'
class FormatManager():
def fillable_extensions(self) -> list[str]:
formats = self.fillable()

View File

@ -19,6 +19,7 @@ from unittest import TestCase
from msgspec.json import decode
from . import Format, FormatManager
class FormatTests(TestCase):
json = \
'''
@ -35,6 +36,7 @@ class FormatTests(TestCase):
form = decode(self.json, type=Format)
self.assertEqual(form.extension(), '.djvu')
class FormatManagerAllTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -42,6 +44,7 @@ class FormatManagerAllTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerDocumentsTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -49,6 +52,7 @@ class FormatManagerDocumentsTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerPresentationsTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -56,6 +60,7 @@ class FormatManagerPresentationsTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerSpreadsheetsTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -63,6 +68,7 @@ class FormatManagerSpreadsheetsTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerConvertibleTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -70,6 +76,7 @@ class FormatManagerConvertibleTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerEditableTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -77,6 +84,7 @@ class FormatManagerEditableTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerViewableTests(TestCase):
def test_loads(self):
format_manager = FormatManager()
@ -84,6 +92,7 @@ class FormatManagerViewableTests(TestCase):
empty = len(formats) == 0
self.assertFalse(empty)
class FormatManagerFillableTests(TestCase):
def test_loads(self):
format_manager = FormatManager()

View File

@ -17,6 +17,7 @@
from unittest import TestCase
from . import memoize
class MemoizeMock():
counter: int = 1
@ -24,6 +25,7 @@ class MemoizeMock():
def method(self) -> int:
return self.counter
class MemoizeTests(TestCase):
def test(self):
mock = MemoizeMock()

View File

@ -17,6 +17,7 @@
from urllib.parse import ParseResult
from src.configuration import ConfigurationManager
class ProxyManager():
config_manager: ConfigurationManager

View File

@ -20,6 +20,7 @@ from urllib.parse import urlparse
from src.configuration import ConfigurationManager
from . import ProxyManager
class ProxyManagerTests(TestCase):
@patch.object(
ConfigurationManager,

View File

@ -18,6 +18,7 @@ from http import HTTPStatus
from json import dumps
from django.http import HttpResponse
class ErrorResponse(HttpResponse):
def __init__(self, message: str, status: HTTPStatus):
payload = {

View File

@ -21,13 +21,13 @@ import os
import shutil
import io
import re
import requests
import time
import urllib.parse
import requests
import magic
from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect, FileResponse
from django.http import FileResponse
from src.configuration import ConfigurationManager
from src.format import FormatManager
from . import fileUtils, historyManager
@ -35,25 +35,31 @@ from . import fileUtils, historyManager
config_manager = ConfigurationManager()
format_manager = FormatManager()
def isCanFillForms(ext):
return ext in format_manager.fillable_extensions()
# check if the file extension can be viewed
def isCanView(ext):
return ext in format_manager.viewable_extensions()
# check if the file extension can be edited
def isCanEdit(ext):
return ext in format_manager.editable_extensions()
# check if the file extension can be converted
def isCanConvert(ext):
return ext in format_manager.convertible_extensions()
# check if the file extension is supported by the editor (it can be viewed or edited or converted)
def isSupportedExt(ext):
return isCanView(ext) | isCanEdit(ext) | isCanConvert(ext) | isCanFillForms(ext)
# get internal extension for a given file type
def getInternalExtension(fileType):
mapping = {
@ -63,7 +69,8 @@ def getInternalExtension(fileType):
'docxf': '.docxf'
}
return mapping.get(fileType, '.docx') # the default file type is .docx
return mapping.get(fileType, '.docx') # the default file type is .docx
# get image url for templates
def getTemplateImageUrl(fileType, request):
@ -74,28 +81,32 @@ def getTemplateImageUrl(fileType, request):
'slide': path + 'file_pptx.svg'
}
return mapping.get(fileType, path + 'file_docx.svg') # the default file type
return mapping.get(fileType, path + 'file_docx.svg') # the default file type
# get file name with an index if such a file name already exists
def getCorrectName(filename, req):
basename = fileUtils.getFileNameWithoutExt(filename)
maxName = 50
basename = fileUtils.getFileNameWithoutExt(filename)[0:maxName] + ('', '[...]')[len(filename) > maxName]
ext = fileUtils.getFileExt(filename)
name = f'{basename}{ext}'
i = 1
while os.path.exists(getStoragePath(name, req)): # if file with such a name already exists
while os.path.exists(getStoragePath(name, req)): # if file with such a name already exists
name = f'{basename} ({i}){ext}' # add an index to its name
i += 1
return name
# get server url
def getServerUrl (forDocumentServer, req):
def getServerUrl(forDocumentServer, req):
example_url = config_manager.example_url()
if (forDocumentServer and example_url is not None):
return example_url.geturl()
else:
return req.headers.get("x-forwarded-proto") or req.scheme + "://" + req.get_host()
return req.headers.get("x-forwarded-proto") or req.scheme + "://" + req.get_host()
# get file url
def getFileUri(filename, forDocumentServer, req):
@ -103,23 +114,27 @@ def getFileUri(filename, forDocumentServer, req):
curAdr = req.META['REMOTE_ADDR']
return f'{host}{settings.STATIC_URL}{curAdr}/{filename}'
# get absolute URL to the document storage service
def getCallbackUrl(filename, req):
host = getServerUrl(True, req)
curAdr = req.META['REMOTE_ADDR']
return f'{host}/track?filename={filename}&userAddress={curAdr}'
# get url to the created file
def getCreateUrl(fileType, req):
host = getServerUrl(False, req)
return f'{host}/create?fileType={fileType}'
# get url to download a file
def getDownloadUrl(filename, req, isServerUrl = True):
def getDownloadUrl(filename, req, isServerUrl=True):
host = getServerUrl(isServerUrl, req)
curAdr = f'&userAddress={req.META["REMOTE_ADDR"]}' if isServerUrl else ""
return f'{host}/download?fileName={filename}{curAdr}'
# get root folder for the current file
def getRootFolder(req):
if isinstance(req, str):
@ -130,11 +145,12 @@ def getRootFolder(req):
storage_directory = config_manager.storage_path()
directory = storage_directory.joinpath(curAdr)
if not os.path.exists(directory): # if such a directory does not exist, make it
if not os.path.exists(directory): # if such a directory does not exist, make it
os.makedirs(directory)
return directory
# get the file history path
def getHistoryPath(filename, file, version, req):
if isinstance(req, str):
@ -144,19 +160,21 @@ def getHistoryPath(filename, file, version, req):
storage_directory = config_manager.storage_path()
directory = storage_directory.joinpath(curAdr)
if not os.path.exists(directory): # the directory with host address doesn't exist
if not os.path.exists(directory): # the directory with host address doesn't exist
filePath = os.path.join(getRootFolder(req), f'{filename}-hist', version, file)
else:
filePath = os.path.join(directory, f'{filename}-hist', version, file)
return filePath
# get the file path
def getStoragePath(filename, req):
directory = getRootFolder(req)
return os.path.join(directory, fileUtils.getFileName(filename))
# get the path to the forcesaved file version
def getForcesavePath(filename, req, create):
if isinstance(req, str):
@ -166,142 +184,165 @@ def getForcesavePath(filename, req, create):
storage_directory = config_manager.storage_path()
directory = storage_directory.joinpath(curAdr)
if not os.path.exists(directory): # the directory with host address doesn't exist
if not os.path.exists(directory): # the directory with host address doesn't exist
return ""
directory = os.path.join(directory, f'{filename}-hist') # get the path to the history of the given file
if (not os.path.exists(directory)):
if create: # if the history directory doesn't exist
os.makedirs(directory) # create history directory if it doesn't exist
else: # the history directory doesn't exist and we are not supposed to create it
directory = os.path.join(directory, f'{filename}-hist') # get the path to the history of the given file
if not os.path.exists(directory):
if create: # if the history directory doesn't exist
os.makedirs(directory) # create history directory if it doesn't exist
else: # the history directory doesn't exist and we are not supposed to create it
return ""
directory = os.path.join(directory, filename) # and get the path to the given file
directory = os.path.join(directory, filename) # and get the path to the given file
if (not os.path.exists(directory) and not create):
return ""
return directory
# get information about all the stored files
def getStoredFiles(req):
directory = getRootFolder(req)
files = os.listdir(directory)
files.sort(key=lambda x: os.path.getmtime(os.path.join(directory, x)), reverse=True) # sort files by time of last modification
# sort files by time of last modification
files.sort(key=lambda x: os.path.getmtime(os.path.join(directory, x)), reverse=True)
fileInfos = []
for f in files:
if os.path.isfile(os.path.join(directory, f)):
fileInfos.append({'isFillFormDoc': isCanFillForms(fileUtils.getFileExt(f)),'version':historyManager.getFileVersion(historyManager.getHistoryDir(getStoragePath(f, req))), 'type': fileUtils.getFileType(f), 'title': f, 'url': getFileUri(f, True, req), 'canEdit': isCanEdit(fileUtils.getFileExt(f))}) # write information about file type, title and url
fileInfos.append({
'isFillFormDoc': isCanFillForms(fileUtils.getFileExt(f)),
'version': historyManager.getFileVersion(historyManager.getHistoryDir(getStoragePath(f, req))),
'type': fileUtils.getFileType(f),
'title': f,
'url': getFileUri(f, True, req),
'canEdit': isCanEdit(fileUtils.getFileExt(f))
}) # write information about file type, title and url
return fileInfos
# create a file
def createFile(stream, path, req = None, meta = False):
def createFile(stream, path, req=None, meta=False):
bufSize = 8192
with io.open(path, 'wb') as out: # write data to the file by streams
with io.open(path, 'wb') as out: # write data to the file by streams
read = stream.read(bufSize)
while len(read) > 0:
out.write(read)
read = stream.read(bufSize)
if meta:
historyManager.createMeta(path, req) # create meta data for the file if needed
return
historyManager.createMeta(path, req) # create meta data for the file if needed
# save file
def saveFile(response, path):
with open(path, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
return
# download file from the given url
def downloadFileFromUri(uri, path = None, withSave = False):
resp = requests.get(uri, stream=True, verify = config_manager.ssl_verify_peer_mode_enabled(), timeout=5)
# download file from the given url
def downloadFileFromUri(uri, path=None, withSave=False):
resp = requests.get(uri, stream=True, verify=config_manager.ssl_verify_peer_mode_enabled(), timeout=5)
status_code = resp.status_code
if status_code != 200: # checking status code
raise RuntimeError('Document editing service returned status: %s' % status_code)
raise RuntimeError(f'Document editing service returned status: {status_code}')
if withSave:
if path is None:
raise RuntimeError('Path for saving file is null')
saveFile(resp, path)
return resp
# create sample file
def createSample(fileType, sample, req):
ext = getInternalExtension(fileType) # get the internal extension of the given file type
ext = getInternalExtension(fileType) # get the internal extension of the given file type
if not sample:
sample = 'false'
sampleName = 'sample' if sample == 'true' else 'new' # create sample or new template
filename = getCorrectName(f'{sampleName}{ext}', req) # get file name with an index if such a file name already exists
sampleName = 'sample' if sample == 'true' else 'new' # create sample or new template
# get file name with an index if such a file name already exists
filename = getCorrectName(f'{sampleName}{ext}', req)
path = getStoragePath(filename, req)
with io.open(os.path.join('assets', 'document-templates', 'sample' if sample == 'true' else 'new', f'{sampleName}{ext}'), 'rb') as stream: # create sample file of the necessary extension in the directory
# create sample file of the necessary extension in the directory
with io.open(os.path.join('assets', 'document-templates', 'sample' if sample == 'true' else 'new',
f'{sampleName}{ext}'), 'rb') as stream:
createFile(stream, path, req, True)
return filename
# remove file from the directory
def removeFile(filename, req):
path = getStoragePath(filename, req)
if os.path.exists(path):
os.remove(path)
histDir = historyManager.getHistoryDir(path) # get history directory
if os.path.exists(histDir): # remove all the history information about this file
histDir = historyManager.getHistoryDir(path) # get history directory
if os.path.exists(histDir): # remove all the history information about this file
shutil.rmtree(histDir)
# generate file key
def generateFileKey(filename, req):
path = getStoragePath(filename, req)
uri = getFileUri(filename, False, req)
stat = os.stat(path) # get the directory parameters
h = str(hash(f'{uri}_{stat.st_mtime_ns}')) # get the hash value of the file url and the date of its last modification and turn it into a string format
stat = os.stat(path) # get the directory parameters
# get the hash value of the file url and the date of its last modification and turn it into a string format
h = str(hash(f'{uri}_{stat.st_mtime_ns}'))
replaced = re.sub(r'[^0-9-.a-zA-Z_=]', '_', h)
return replaced[:20] # take the first 20 characters for the key
return replaced[:20] # take the first 20 characters for the key
# generate the document key value
def generateRevisionId(expectedKey):
if (len(expectedKey) > 20):
if len(expectedKey) > 20:
expectedKey = str(hash(expectedKey))
key = re.sub(r'[^0-9-.a-zA-Z_=]', '_', expectedKey)
return key[:20]
# get files information
def getFilesInfo(req):
fileId = req.GET.get('fileId') if req.GET.get('fileId') else None
result = []
resultID = []
for f in getStoredFiles(req): # run through all the files from the directory
stats = os.stat(os.path.join(getRootFolder(req), f.get("title"))) # get file information
result.append( # write file parameters to the file object
{ "version" : historyManager.getFileVersion(historyManager.getHistoryDir(getStoragePath(f.get("title"), req))),
"id" : generateFileKey(f.get("title"), req),
"contentLength" : "%.2f KB" % (stats.st_size/1024),
"pureContentLength" : stats.st_size,
"title" : f.get("title"),
"updated" : time.strftime("%Y-%m-%dT%X%z",time.gmtime(stats.st_mtime))
})
if fileId : # if file id is defined
if fileId == generateFileKey(f.get("title"), req) : # and it is equal to the file key value
resultID.append(result[-1]) # add file object to the response array
for f in getStoredFiles(req): # run through all the files from the directory
stats = os.stat(os.path.join(getRootFolder(req), f.get("title"))) # get file information
result.append( # write file parameters to the file object
{
"version": historyManager.getFileVersion(historyManager.getHistoryDir(
getStoragePath(f.get("title"), req)
)),
"id": generateFileKey(f.get("title"), req),
"contentLength": f"{(stats.st_size/1024):.2f} KB",
"pureContentLength": stats.st_size,
"title": f.get("title"),
"updated": time.strftime("%Y-%m-%dT%X%z", time.gmtime(stats.st_mtime))
})
if fileId: # if file id is defined
if fileId == generateFileKey(f.get("title"), req): # and it is equal to the file key value
resultID.append(result[-1]) # add file object to the response array
if fileId:
if len(resultID) > 0:
return resultID
return "File not found"
return result
if fileId :
if len(resultID) > 0 : return resultID
else : return "File not found"
else :
return result
# download the file
def download(filePath):
response = FileResponse(open(filePath, 'rb'), True) # write headers to the response object
response['Content-Length'] = os.path.getsize(filePath)
response['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + urllib.parse.quote_plus(os.path.basename(filePath))
response = FileResponse(open(filePath, 'rb'), True) # write headers to the response object
response['Content-Length'] = os.path.getsize(filePath)
response['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + \
urllib.parse.quote_plus(os.path.basename(filePath))
response['Content-Type'] = magic.from_file(filePath, mime=True)
response['Access-Control-Allow-Origin'] = "*"
return response
return response

View File

@ -22,26 +22,30 @@ from src.format import FormatManager
config_manager = ConfigurationManager()
format_manager = FormatManager()
# get file name from the document url
def getFileName(str):
ind = str.rfind('/')
return str[ind+1:]
def getFileName(uri):
ind = uri.rfind('/')
return uri[ind+1:]
# get file name without extension from the document url
def getFileNameWithoutExt(str):
fn = getFileName(str)
def getFileNameWithoutExt(uri):
fn = getFileName(uri)
ind = fn.rfind('.')
return fn[:ind]
# get file extension from the document url
def getFileExt(str):
fn = getFileName(str)
def getFileExt(uri):
fn = getFileName(uri)
ind = fn.rfind('.')
return fn[ind:].lower()
# get file type
def getFileType(str):
ext = getFileExt(str)
def getFileType(uri):
ext = getFileExt(uri)
if ext in format_manager.document_extensions():
return 'word'
if ext in format_manager.spreadsheet_extensions():
@ -49,4 +53,4 @@ def getFileType(str):
if ext in format_manager.presentation_extensions():
return 'slide'
return 'word' # default file type is word
return 'word' # default file type is word

View File

@ -20,90 +20,99 @@ import os
import io
import json
from . import users, fileUtils
from datetime import datetime
from src.utils import docManager
from src.utils import jwtManager
from . import users, fileUtils
# get the path to the history direction
def getHistoryDir(storagePath):
return f'{storagePath}-hist'
# get the path to the given file version
def getVersionDir(histDir, version):
return os.path.join(histDir, str(version))
# get file version of the given history directory
def getFileVersion(histDir):
if not os.path.exists(histDir): # if the history directory doesn't exist
return 0 # file version is 0
if not os.path.exists(histDir): # if the history directory doesn't exist
return 0 # file version is 0
cnt = 1
for f in os.listdir(histDir): # run through all the files in the history directory
if not os.path.isfile(os.path.join(histDir, f)): # and count the number of files
for f in os.listdir(histDir): # run through all the files in the history directory
if not os.path.isfile(os.path.join(histDir, f)): # and count the number of files
cnt += 1
return cnt
# get the path to the next file version
def getNextVersionDir(histDir):
v = getFileVersion(histDir) # get file version of the given history directory
path = getVersionDir(histDir, v) # get the path to the next file version
v = getFileVersion(histDir) # get file version of the given history directory
path = getVersionDir(histDir, v) # get the path to the next file version
if not os.path.exists(path): # if this path doesn't exist
os.makedirs(path) # make the directory for this file version
if not os.path.exists(path): # if this path doesn't exist
os.makedirs(path) # make the directory for this file version
return path
# get the path to a file archive with differences in the given file version
def getChangesZipPath(verDir):
return os.path.join(verDir, 'diff.zip')
# get the path to a json file with changes of the given file version
def getChangesHistoryPath(verDir):
return os.path.join(verDir, 'changes.json')
# get the path to the previous file version
def getPrevFilePath(verDir, ext):
return os.path.join(verDir, f'prev{ext}')
# get the path to a txt file with a key information in it
def getKeyPath(verDir):
return os.path.join(verDir, 'key.txt')
# get the path to a json file with meta data about this file
def getMetaPath(histDir):
return os.path.join(histDir, 'createdInfo.json')
# create a json file with file meta data using the storage path and request
def createMeta(storagePath, req):
histDir = getHistoryDir(storagePath)
path = getMetaPath(histDir) # get the path to a json file with meta data about file
path = getMetaPath(histDir) # get the path to a json file with meta data about file
if not os.path.exists(histDir):
os.makedirs(histDir)
user = users.getUserFromReq(req) # get the user information (id and name)
user = users.getUserFromReq(req) # get the user information (id and name)
obj = { # create the meta data object
obj = { # create the meta data object
'created': datetime.today().strftime('%Y-%m-%d %H:%M:%S'),
'uid': user.id,
'uname': user.name
}
writeFile(path, json.dumps(obj))
return
# create a json file with file meta data using the file name, user id, user name and user address
def createMetaData(filename, uid, uname, usAddr):
histDir = getHistoryDir(docManager.getStoragePath(filename, usAddr))
path = getMetaPath(histDir) # get the path to a json file with meta data about file
path = getMetaPath(histDir) # get the path to a json file with meta data about file
if not os.path.exists(histDir):
os.makedirs(histDir)
obj = { # create the meta data object
obj = { # create the meta data object
'created': datetime.today().strftime('%Y-%m-%d %H:%M:%S'),
'uid': uid,
'uname': uname
@ -111,52 +120,54 @@ def createMetaData(filename, uid, uname, usAddr):
writeFile(path, json.dumps(obj))
return
# create file with a given content in it
def writeFile(path, content):
with io.open(path, 'w') as out:
out.write(content)
return
# read a file
def readFile(path):
with io.open(path, 'r') as stream:
return stream.read()
# get the url to the history file version with a given extension
def getPublicHistUri(filename, ver, file, req, isServerUrl=True):
host = docManager.getServerUrl(isServerUrl, req)
curAdr = f'&userAddress={req.META["REMOTE_ADDR"]}' if isServerUrl else ''
return f'{host}/downloadhistory?fileName={filename}&ver={ver}&file={file}{curAdr}'
# get the meta data of the file
def getMeta(storagePath):
histDir = getHistoryDir(storagePath)
path = getMetaPath(histDir)
if os.path.exists(path): # check if the json file with file meta data exists
if os.path.exists(path): # check if the json file with file meta data exists
with io.open(path, 'r') as stream:
return json.loads(stream.read()) # turn meta data into python format
return json.loads(stream.read()) # turn meta data into python format
return None
# get the document history of a given file
def getHistoryObject(storagePath, filename, docKey, docUrl, isEnableDirectUrl, req):
histDir = getHistoryDir(storagePath)
version = getFileVersion(histDir)
if version > 0: # if the file was modified (the file version is greater than 0)
if version > 0: # if the file was modified (the file version is greater than 0)
hist = []
histData = {}
for i in range(1, version + 1): # run through all the file versions
for i in range(1, version + 1): # run through all the file versions
obj = {}
dataObj = {}
prevVerDir = getVersionDir(histDir, i - 1) # get the path to the previous file version
verDir = getVersionDir(histDir, i) # get the path to the given file version
prevVerDir = getVersionDir(histDir, i - 1) # get the path to the previous file version
verDir = getVersionDir(histDir, i) # get the path to the given file version
try:
key = docKey if i == version else readFile(getKeyPath(verDir)) # get document key
key = docKey if i == version else readFile(getKeyPath(verDir)) # get document key
obj['key'] = key
obj['version'] = i
@ -164,56 +175,62 @@ def getHistoryObject(storagePath, filename, docKey, docUrl, isEnableDirectUrl, r
dataObj['key'] = key
dataObj['version'] = i
if i == 1: # check if the version number is equal to 1
meta = getMeta(storagePath) # get meta data of this file
if meta: # write meta information to the object (user information and creation date)
if i == 1: # check if the version number is equal to 1
meta = getMeta(storagePath) # get meta data of this file
if meta: # write meta information to the object (user information and creation date)
obj['created'] = meta['created']
obj['user'] = {
'id': meta['uid'],
'name': meta['uname']
}
dataObj['url'] = docUrl if i == version else getPublicHistUri(filename, i, "prev" + fileUtils.getFileExt(filename), req) # write file url to the data object
# write file url to the data object
dataObj['url'] = docUrl if i == version else getPublicHistUri(
filename, i, "prev" + fileUtils.getFileExt(filename), req
)
if isEnableDirectUrl:
dataObj['directUrl'] = docManager.getDownloadUrl(filename, req, False) if i == version else getPublicHistUri(filename, i, "prev" + fileUtils.getFileExt(filename), req, False) # write file direct url to the data object
# write file direct url to the data object
dataObj['directUrl'] = docManager.getDownloadUrl(filename, req, False) if i == version \
else getPublicHistUri(filename, i, "prev" + fileUtils.getFileExt(filename), req, False)
if i > 1: # check if the version number is greater than 1 (the file was modified)
changes = json.loads(readFile(getChangesHistoryPath(prevVerDir))) # get the path to the changes.json file
if i > 1: # check if the version number is greater than 1 (the file was modified)
# get the path to the changes.json file
changes = json.loads(readFile(getChangesHistoryPath(prevVerDir)))
change = changes['changes'][0]
obj['changes'] = changes['changes'] if change else None # write information about changes to the object
# write information about changes to the object
obj['changes'] = changes['changes'] if change else None
obj['serverVersion'] = changes['serverVersion']
obj['created'] = change['created'] if change else None
obj['user'] = change['user'] if change else None
prev = histData[str(i - 2)] # get the history data from the previous file version
prevInfo = { # write key and url information about previous file version
prev = histData[str(i - 2)] # get the history data from the previous file version
prevInfo = { # write key and url information about previous file version
'fileType': prev['fileType'],
'key': prev['key'],
'url': prev['url'],
'directUrl': prev['directUrl']
} if isEnableDirectUrl else { # write key and url information about previous file version
} if isEnableDirectUrl else { # write key and url information about previous file version
'fileType': prev['fileType'],
'key': prev['key'],
'url': prev['url']
}
dataObj['previous'] = prevInfo # write information about previous file version to the data object
dataObj['changesUrl'] = getPublicHistUri(filename, i - 1, "diff.zip", req) # write the path to the diff.zip archive with differences in this file version
dataObj['previous'] = prevInfo # write information about previous file version to the data object
# write the path to the diff.zip archive with differences in this file version
dataObj['changesUrl'] = getPublicHistUri(filename, i - 1, "diff.zip", req)
if jwtManager.isEnabled():
dataObj['token'] = jwtManager.encode(dataObj)
dataObj['token'] = jwtManager.encode(dataObj)
hist.append(obj) # add object dictionary to the hist list
histData[str(i - 1)] = dataObj # write data object information to the history data
hist.append(obj) # add object dictionary to the hist list
histData[str(i - 1)] = dataObj # write data object information to the history data
except Exception:
return {}
histObj = { # write history information about the current file version to the history object
histObj = { # write history information about the current file version to the history object
'currentVersion': version,
'history': hist
}
return { 'history': histObj, 'historyData': histData }
return {'history': histObj, 'historyData': histData}
return {}

View File

@ -21,18 +21,22 @@ from src.configuration import ConfigurationManager
config_manager = ConfigurationManager()
# check if a secret key to generate token exists or not
def isEnabled():
return bool(config_manager.jwt_secret())
# check if a secret key to generate token exists or not
def useForRequest():
return config_manager.jwt_use_for_request()
# encode a payload object into a token using a secret key and decodes it into the utf-8 format
def encode(payload):
return jwt.encode(payload, config_manager.jwt_secret(), algorithm='HS256')
# decode a token into a payload object using a secret key
def decode(string):
return jwt.decode(string, config_manager.jwt_secret(), algorithms=['HS256'])
return jwt.decode(string, config_manager.jwt_secret(), algorithms=['HS256'])

View File

@ -16,7 +16,6 @@
"""
import json
import requests
from src.configuration import ConfigurationManager
@ -24,14 +23,15 @@ from . import fileUtils, jwtManager
config_manager = ConfigurationManager()
# convert file and give url to a new file
def getConvertedData(docUri, fromExt, toExt, docKey, isAsync, filePass = None, lang = None):
if not fromExt: # check if the extension from the request matches the real file extension
fromExt = fileUtils.getFileExt(docUri) # if not, overwrite the extension value
def getConvertedData(docUri, fromExt, toExt, docKey, isAsync, filePass=None, lang=None):
if not fromExt: # check if the extension from the request matches the real file extension
fromExt = fileUtils.getFileExt(docUri) # if not, overwrite the extension value
title = fileUtils.getFileName(docUri)
payload = { # write all the necessary data to the payload object
payload = { # write all the necessary data to the payload object
'url': docUri,
'outputtype': toExt.replace('.', ''),
'filetype': fromExt.replace('.', ''),
@ -41,24 +41,27 @@ def getConvertedData(docUri, fromExt, toExt, docKey, isAsync, filePass = None, l
'region': lang
}
headers={'accept': 'application/json'}
headers = {'accept': 'application/json'}
if (isAsync): # check if the operation is asynchronous
payload.setdefault('async', True) # and write this information to the payload object
if isAsync: # check if the operation is asynchronous
payload.setdefault('async', True) # and write this information to the payload object
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # check if a secret key to generate token exists or not
headerToken = jwtManager.encode({'payload': payload}) # encode a payload object into a header token
payload['token'] = jwtManager.encode(payload) # encode a payload object into a body token
headers[config_manager.jwt_header()] = f'Bearer {headerToken}' # add a header Authorization with a header token with Authorization prefix in it
response = requests.post(config_manager.document_server_converter_url().geturl(), json=payload, headers=headers, verify = config_manager.ssl_verify_peer_mode_enabled(), timeout=5) # send the headers and body values to the converter and write the result to the response
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # check if a secret key to generate token exists or not
headerToken = jwtManager.encode({'payload': payload}) # encode a payload object into a header token
payload['token'] = jwtManager.encode(payload) # encode a payload object into a body token
# add a header Authorization with a header token with Authorization prefix in it
headers[config_manager.jwt_header()] = f'Bearer {headerToken}'
# send the headers and body values to the converter and write the result to the response
response = requests.post(config_manager.document_server_converter_url().geturl(), json=payload, headers=headers,
verify=config_manager.ssl_verify_peer_mode_enabled(), timeout=5)
status_code = response.status_code
if status_code != 200: # checking status code
raise RuntimeError('Convertation service returned status: %s' % status_code)
raise RuntimeError(f'Convertation service returned status: {status_code}')
json = response.json()
return getResponseUri(json)
# get response url
def getResponseUri(json):
isEnd = json.get('endConvert')
@ -67,7 +70,8 @@ def getResponseUri(json):
processError(error)
if isEnd:
return { 'uri': json.get('fileUrl'), 'fileType': json.get('fileType') }
return {'uri': json.get('fileUrl'), 'fileType': json.get('fileType')}
# display an error that occurs during conversion
def processError(error):
@ -84,4 +88,4 @@ def processError(error):
'-1': f'{prefix}Error convertation unknown'
}
raise Exception(mapping.get(str(error), f'Error Code: {error}'))
raise Exception(mapping.get(str(error), f'Error Code: {error}'))

View File

@ -28,101 +28,110 @@ from . import jwtManager, docManager, historyManager, fileUtils, serviceConverte
config_manager = ConfigurationManager()
proxy_manager = ProxyManager(config_manager=config_manager)
# read request body
def readBody(request):
body = json.loads(request.body)
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # if the secret key to generate token exists
token = body.get('token') # get the document token
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # if the secret key to generate token exists
token = body.get('token') # get the document token
if (not token): # if JSON web token is not received
token = request.headers.get(config_manager.jwt_header()) # get it from the Authorization header
if not token: # if JSON web token is not received
token = request.headers.get(config_manager.jwt_header()) # get it from the Authorization header
if token:
token = token[len('Bearer '):] # and save it without Authorization prefix
token = token[len('Bearer '):] # and save it without Authorization prefix
if (not token): # if the token is not received
raise Exception('Expected JWT') # an error occurs
if not token: # if the token is not received
raise Exception('Expected JWT') # an error occurs
body = jwtManager.decode(token)
if (body.get('payload')): # get the payload object from the request body
if body.get('payload'): # get the payload object from the request body
body = body['payload']
return body
# file saving process
def processSave(raw_body, filename, usAddr):
body = resolve_process_save_body(raw_body)
download = body.get('url')
if (download is None):
if download is None:
raise Exception("DownloadUrl is null")
changesUri = body.get('changesurl')
newFilename = filename
curExt = fileUtils.getFileExt(filename) # get current file extension
curExt = fileUtils.getFileExt(filename) # get current file extension
downloadExt = "." + body.get('filetype') # get the extension of the downloaded file
# convert downloaded file to the file with the current extension if these extensions aren't equal
if (curExt != downloadExt):
if curExt != downloadExt:
try:
convertedData = serviceConverter.getConvertedData(download, downloadExt, curExt, docManager.generateRevisionId(download), False) # convert file and give url to a new file
# convert file and give url to a new file
convertedData = serviceConverter.getConvertedData(download, downloadExt, curExt,
docManager.generateRevisionId(download), False)
if not convertedData:
newFilename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + downloadExt, usAddr) # get the correct file name if it already exists
newFilename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + downloadExt,
usAddr) # get the correct file name if it already exists
else:
download = convertedData['uri']
except Exception:
newFilename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + downloadExt, usAddr)
path = docManager.getStoragePath(newFilename, usAddr) # get the file path
path = docManager.getStoragePath(newFilename, usAddr) # get the file path
data = docManager.downloadFileFromUri(download) # download document file
if (data is None):
if data is None:
raise Exception("Downloaded document is null")
histDir = historyManager.getHistoryDir(path) # get the path to the history direction
if not os.path.exists(histDir): # if the path doesn't exist
os.makedirs(histDir) # create it
histDir = historyManager.getHistoryDir(path) # get the path to the history direction
if not os.path.exists(histDir): # if the path doesn't exist
os.makedirs(histDir) # create it
versionDir = historyManager.getNextVersionDir(histDir) # get the path to the next file version
versionDir = historyManager.getNextVersionDir(histDir) # get the path to the next file version
os.rename(docManager.getStoragePath(filename, usAddr), historyManager.getPrevFilePath(versionDir, curExt)) # get the path to the previous file version and rename the storage path with it
# get the path to the previous file version and rename the storage path with it
os.rename(docManager.getStoragePath(filename, usAddr), historyManager.getPrevFilePath(versionDir, curExt))
docManager.saveFile(data, path) # save document file
dataChanges = docManager.downloadFileFromUri(changesUri) # download changes file
if (dataChanges is None):
dataChanges = docManager.downloadFileFromUri(changesUri) # download changes file
if dataChanges is None:
raise Exception("Downloaded changes is null")
docManager.saveFile(dataChanges, historyManager.getChangesZipPath(versionDir)) # save file changes to the diff.zip archive
# save file changes to the diff.zip archive
docManager.saveFile(dataChanges, historyManager.getChangesZipPath(versionDir))
hist = None
hist = body.get('changeshistory')
if (not hist) & ('history' in body):
hist = json.dumps(body.get('history'))
if hist:
historyManager.writeFile(historyManager.getChangesHistoryPath(versionDir), hist) # write the history changes to the changes.json file
# write the history changes to the changes.json file
historyManager.writeFile(historyManager.getChangesHistoryPath(versionDir), hist)
# write the key value to the key.txt file
historyManager.writeFile(historyManager.getKeyPath(versionDir), body.get('key'))
# get the path to the forcesaved file version
forcesavePath = docManager.getForcesavePath(newFilename, usAddr, False)
if forcesavePath != "": # if the forcesaved file version exists
os.remove(forcesavePath) # remove it
historyManager.writeFile(historyManager.getKeyPath(versionDir), body.get('key')) # write the key value to the key.txt file
forcesavePath = docManager.getForcesavePath(newFilename, usAddr, False) # get the path to the forcesaved file version
if (forcesavePath != ""): # if the forcesaved file version exists
os.remove(forcesavePath) # remove it
return
# file force saving process
def processForceSave(body, filename, usAddr):
download = body.get('url')
if (download is None):
if download is None:
raise Exception("DownloadUrl is null")
curExt = fileUtils.getFileExt(filename) # get current file extension
curExt = fileUtils.getFileExt(filename) # get current file extension
downloadExt = "." + body.get('filetype') # get the extension of the downloaded file
newFilename = False
# convert downloaded file to the file with the current extension if these extensions aren't equal
if (curExt != downloadExt):
if curExt != downloadExt:
try:
convertedData = serviceConverter.getConvertedData(download, downloadExt, curExt, docManager.generateRevisionId(download), False) # convert file and give url to a new file
# convert file and give url to a new file
convertedData = serviceConverter.getConvertedData(download, downloadExt, curExt,
docManager.generateRevisionId(download), False)
if not convertedData:
newFilename = True
else:
@ -131,56 +140,59 @@ def processForceSave(body, filename, usAddr):
newFilename = True
data = docManager.downloadFileFromUri(download) # download document file
if (data is None):
if data is None:
raise Exception("Downloaded document is null")
isSubmitForm = body.get('forcesavetype') == 3 # SubmitForm
isSubmitForm = body.get('forcesavetype') == 3 # SubmitForm
if(isSubmitForm):
if (newFilename):
filename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + "-form" + downloadExt, usAddr) # get the correct file name if it already exists
else :
if isSubmitForm:
if newFilename:
filename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + "-form" + downloadExt,
usAddr) # get the correct file name if it already exists
else:
filename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + "-form" + curExt, usAddr)
forcesavePath = docManager.getStoragePath(filename, usAddr)
else:
if (newFilename):
if newFilename:
filename = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + downloadExt, usAddr)
forcesavePath = docManager.getForcesavePath(filename, usAddr, False)
if (forcesavePath == ""):
if forcesavePath == "":
forcesavePath = docManager.getForcesavePath(filename, usAddr, True)
docManager.saveFile(download, forcesavePath) # save document file
docManager.saveFile(download, forcesavePath) # save document file
if isSubmitForm:
uid = body['actions'][0]['userid'] # get the user id
historyManager.createMetaData(filename, uid, "Filling Form", usAddr) # create meta data for forcesaved file
if(isSubmitForm):
uid = body['actions'][0]['userid'] # get the user id
historyManager.createMetaData(filename, uid, "Filling Form", usAddr) # create meta data for forcesaved file
return
# create a command request
def commandRequest(method, key, meta = None):
def commandRequest(method, key, meta=None):
payload = {
'c': method,
'key': key
}
if (meta):
if meta:
payload['meta'] = meta
headers = {'accept': 'application/json'}
headers={'accept': 'application/json'}
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # check if a secret key to generate token exists or not
headerToken = jwtManager.encode({'payload': payload}) # encode a payload object into a header token
# add a header Authorization with a header token with Authorization prefix in it
headers[config_manager.jwt_header()] = f'Bearer {headerToken}'
if (jwtManager.isEnabled() and jwtManager.useForRequest()): # check if a secret key to generate token exists or not
headerToken = jwtManager.encode({'payload': payload}) # encode a payload object into a header token
headers[config_manager.jwt_header()] = f'Bearer {headerToken}' # add a header Authorization with a header token with Authorization prefix in it
payload['token'] = jwtManager.encode(payload) # encode a payload object into a body token
response = requests.post(config_manager.document_server_command_url().geturl(), json=payload, headers=headers,
verify=config_manager.ssl_verify_peer_mode_enabled(), timeout=5)
payload['token'] = jwtManager.encode(payload) # encode a payload object into a body token
response = requests.post(config_manager.document_server_command_url().geturl(), json=payload, headers=headers, verify = config_manager.ssl_verify_peer_mode_enabled())
if (meta):
if meta:
return response
return
def resolve_process_save_body(body):
copied = deepcopy(body)

View File

@ -18,9 +18,11 @@
from typing import Optional
class User:
def __init__(self, id, name, email, group, reviewGroups, commentGroups, userInfoGroups, favorite, deniedPermissions, descriptions, templates):
self.id = id
def __init__(self, uid, name, email, group, reviewGroups, commentGroups, userInfoGroups, favorite,
deniedPermissions, descriptions, templates):
self.id = uid
self.name = name
self.email = email
self.group = group
@ -32,6 +34,7 @@ class User:
self.templates = templates
self.userInfoGroups = userInfoGroups
descr_user_1 = [
"File author by default",
"Doesnt belong to any group",
@ -45,7 +48,8 @@ descr_user_1 = [
descr_user_2 = [
"Belongs to Group2",
"Can review only his own changes or changes made by users with no group",
"Can view comments, edit his own comments and comments left by users with no group. Can remove his own comments only",
("Can view comments, edit his own comments and comments left by users with no group."
"Can remove his own comments only"),
"This file is marked as favorite",
"Can create new files from the editor",
"Can see the information about users from Group2 and users who dont belong to any group"
@ -80,57 +84,61 @@ descr_user_0 = [
USERS = [
User('uid-1', 'John Smith', 'smith@example.com',
'', None, {}, None,
None, [], descr_user_1, True),
'', None, {}, None,
None, [], descr_user_1, True),
User('uid-2', 'Mark Pottato', 'pottato@example.com',
'group-2', ['group-2', ''], {
'view': "",
'edit': ["group-2", ""],
'remove': ["group-2"]
},
'group-2', ['group-2', ''], {
'view': "",
'edit': ["group-2", ""],
'remove': ["group-2"]
},
['group-2', ''],
True, [], descr_user_2, False),
True, [], descr_user_2, False),
User('uid-3', 'Hamish Mitchell', 'mitchell@example.com',
'group-3', ['group-2'], {
'view': ["group-3", "group-2"],
'edit': ["group-2"],
'remove': []
}, ['group-2'],
False, ["copy", "download", "print"], descr_user_3, False),
'group-3', ['group-2'], {
'view': ["group-3", "group-2"],
'edit': ["group-2"],
'remove': []
}, ['group-2'],
False, ["copy", "download", "print"], descr_user_3, False),
User('uid-0', None, None,
'', None, {}, [],
None, ["protect"], descr_user_0, False)
'', None, {}, [],
None, ["protect"], descr_user_0, False)
]
DEFAULT_USER = USERS[0]
# get all users
def getAllUsers():
return USERS
# get user information from the request
def getUserFromReq(req):
uid = req.COOKIES.get('uid')
for user in USERS:
if (user.id == uid):
if user.id == uid:
return user
return DEFAULT_USER
# get users data for mentions
def getUsersForMentions(uid):
usersData = []
for user in USERS:
if(user.id != uid and user.name != None and user.email != None):
usersData.append({'name':user.name, 'email':user.email})
if (user.id != uid and user.name is not None and user.email is not None):
usersData.append({'name': user.name, 'email': user.email})
return usersData
def find_user(id: Optional[str]) -> User:
if id is None:
def find_user(searchId: Optional[str]) -> User:
if searchId is None:
return DEFAULT_USER
for user in USERS:
if not user.id == id:
if not user.id == searchId:
continue
return user
return DEFAULT_USER

View File

@ -32,23 +32,27 @@ from src.utils import docManager, fileUtils, serviceConverter, users, jwtManager
config_manager = ConfigurationManager()
# upload a file from the document storage service to the document editing service
def upload(request):
response = {}
try:
fileInfo = request.FILES['uploadedFile']
if ((fileInfo.size > config_manager.maximum_file_size()) | (fileInfo.size <= 0)): # check if the file size exceeds the maximum size allowed (5242880)
# check if the file size exceeds the maximum size allowed (5242880)
if (fileInfo.size > config_manager.maximum_file_size()) | (fileInfo.size <= 0):
raise Exception('File size is incorrect')
curExt = fileUtils.getFileExt(fileInfo.name)
if not docManager.isSupportedExt(curExt): # check if the file extension is supported by the document manager
raise Exception('File type is not supported')
name = docManager.getCorrectName(fileInfo.name, request) # get file name with an index if such a file name already exists
# get file name with an index if such a file name already exists
name = docManager.getCorrectName(fileInfo.name, request)
path = docManager.getStoragePath(name, request)
docManager.createFile(fileInfo.file, path, request, True) # create file with meta information in the storage directory
# create file with meta information in the storage directory
docManager.createFile(fileInfo.file, path, request, True)
response.setdefault('filename', name)
response.setdefault('documentType', fileUtils.getFileType(name))
@ -58,6 +62,7 @@ def upload(request):
return HttpResponse(json.dumps(response), content_type='application/json') # return http response in json format
# convert a file from one format to another
def convert(request):
response = {}
@ -67,32 +72,39 @@ def convert(request):
filename = fileUtils.getFileName(body.get("filename"))
filePass = body.get("filePass")
lang = request.COOKIES.get('ulang') if request.COOKIES.get('ulang') else 'en'
fileUri = docManager.getDownloadUrl(filename,request)
fileUri = docManager.getDownloadUrl(filename, request)
fileExt = fileUtils.getFileExt(filename)
newExt = 'ooxml' # convert to .ooxml
if docManager.isCanConvert(fileExt): # check if the file extension is available for converting
key = docManager.generateFileKey(filename, request) # generate the file key
convertedData = serviceConverter.getConvertedData(fileUri, fileExt, newExt, key, True, filePass, lang) # get the url of the converted file
# get the url of the converted file
convertedData = serviceConverter.getConvertedData(fileUri, fileExt, newExt, key, True, filePass, lang)
if not convertedData: # if the converter url is not received, the original file name is passed to the response
# if the converter url is not received, the original file name is passed to the response
if not convertedData:
response.setdefault('step', '0')
response.setdefault('filename', filename)
else:
correctName = docManager.getCorrectName(fileUtils.getFileNameWithoutExt(filename) + '.' + convertedData['fileType'], request) # otherwise, create a new name with the necessary extension
correctName = docManager.getCorrectName(
fileUtils.getFileNameWithoutExt(filename) + '.' + convertedData['fileType'], request
) # otherwise, create a new name with the necessary extension
path = docManager.getStoragePath(correctName, request)
docManager.downloadFileFromUri(convertedData['uri'], path, True) # save the file from the new url in the storage directory
# save the file from the new url in the storage directory
docManager.downloadFileFromUri(convertedData['uri'], path, True)
docManager.removeFile(filename, request) # remove the original file
response.setdefault('filename', correctName) # pass the name of the converted file to the response
else:
response.setdefault('filename', filename) # if the file can't be converted, the original file name is passed to the response
# if the file can't be converted, the original file name is passed to the response
response.setdefault('filename', filename)
except Exception as e:
response.setdefault('error', e.args[0])
return HttpResponse(json.dumps(response), content_type='application/json')
# create a new file
def createNew(request):
response = {}
@ -110,6 +122,7 @@ def createNew(request):
return HttpResponse(json.dumps(response), content_type='application/json')
# save file as...
def saveAs(request):
response = {}
@ -121,9 +134,10 @@ def saveAs(request):
filename = docManager.getCorrectName(title, request)
path = docManager.getStoragePath(filename, request)
resp = requests.get(saveAsFileUrl, verify = config_manager.ssl_verify_peer_mode_enabled())
resp = requests.get(saveAsFileUrl, verify=config_manager.ssl_verify_peer_mode_enabled(), timeout=5)
if ((len(resp.content) > config_manager.maximum_file_size()) | (len(resp.content) <= 0)): # check if the file size exceeds the maximum size allowed (5242880)
# check if the file size exceeds the maximum size allowed (5242880)
if (len(resp.content) > config_manager.maximum_file_size()) | (len(resp.content) <= 0):
response.setdefault('error', 'File size is incorrect')
raise Exception('File size is incorrect')
@ -132,7 +146,8 @@ def saveAs(request):
response.setdefault('error', 'File type is not supported')
raise Exception('File type is not supported')
docManager.downloadFileFromUri(saveAsFileUrl, path, True) # save the file from the new url in the storage directory
# save the file from the new url in the storage directory
docManager.downloadFileFromUri(saveAsFileUrl, path, True)
response.setdefault('file', filename)
except Exception as e:
@ -141,6 +156,7 @@ def saveAs(request):
return HttpResponse(json.dumps(response), content_type='application/json')
# rename file
def rename(request):
response = {}
@ -150,7 +166,7 @@ def rename(request):
origExt = '.' + body['ext']
curExt = fileUtils.getFileExt(newfilename)
if (origExt != curExt):
if origExt != curExt:
newfilename += origExt
dockey = body['dockey']
@ -162,32 +178,36 @@ def rename(request):
return HttpResponse(json.dumps(response), content_type='application/json')
# edit a file
def edit(request):
filename = fileUtils.getFileName(request.GET['filename'])
isEnableDirectUrl = request.GET['directUrl'].lower() in ("true") if 'directUrl' in request.GET else False
isEnableDirectUrl = request.GET['directUrl'].lower() in ("true") if 'directUrl' in request.GET else False
ext = fileUtils.getFileExt(filename)
fileUri = docManager.getFileUri(filename, True, request)
fileUriUser = docManager.getDownloadUrl(filename, request) + "&dmode=emb"
directUrl = docManager.getDownloadUrl(filename, request, False)
docKey = docManager.generateFileKey(filename, request)
fileType = fileUtils.getFileType(filename)
user = users.getUserFromReq(request) # get user
edMode = request.GET.get('mode') if request.GET.get('mode') else 'edit' # get the editor mode: view/edit/review/comment/fillForms/embedded (the default mode is edit)
# get the editor mode: view/edit/review/comment/fillForms/embedded (the default mode is edit)
edMode = request.GET.get('mode') if request.GET.get('mode') else 'edit'
canEdit = docManager.isCanEdit(ext) # check if the file with this extension can be edited
if (((not canEdit) and edMode == 'edit') or edMode == 'fillForms') and docManager.isCanFillForms(ext) :
if (((not canEdit) and edMode == 'edit') or edMode == 'fillForms') and docManager.isCanFillForms(ext):
edMode = 'fillForms'
canEdit = True
submitForm = edMode == 'fillForms' and user.id == 'uid-1' and False # if the Submit form button is displayed or hidden
# if the Submit form button is displayed or hidden
submitForm = edMode == 'fillForms' and user.id == 'uid-1' and False
mode = 'edit' if canEdit & (edMode != 'view') else 'view' # if the file can't be edited, the mode is view
types = ['desktop', 'mobile', 'embedded']
edType = request.GET.get('type') if request.GET.get('type') in types else 'desktop' # get the editor type: embedded/mobile/desktop (the default type is desktop)
lang = request.COOKIES.get('ulang') if request.COOKIES.get('ulang') else 'en' # get the editor language (the default language is English)
# get the editor type: embedded/mobile/desktop (the default type is desktop)
edType = request.GET.get('type') if request.GET.get('type') in types else 'desktop'
# get the editor language (the default language is English)
lang = request.COOKIES.get('ulang') if request.COOKIES.get('ulang') else 'en'
storagePath = docManager.getStoragePath(filename, request)
meta = historyManager.getMeta(storagePath) # get the document meta data
@ -196,7 +216,8 @@ def edit(request):
actionData = request.GET.get('actionLink') # get the action data that will be scrolled to (comment or bookmark)
actionLink = json.loads(actionData) if actionData else None
templatesImageUrl = docManager.getTemplateImageUrl(fileType, request) # templates image url in the "From Template" section
# templates image url in the "From Template" section
templatesImageUrl = docManager.getTemplateImageUrl(fileType, request)
createUrl = docManager.getCreateUrl(edType, request)
templates = [
{
@ -211,7 +232,7 @@ def edit(request):
}
]
if (meta): # if the document meta data exists,
if meta: # if the document meta data exists,
infObj = { # write author and creation time parameters to the information object
'owner': meta['uname'],
'uploaded': meta['created']
@ -234,24 +255,28 @@ def edit(request):
'key': docKey,
'info': infObj,
'permissions': { # the permission for the document to be edited and downloaded or not
'comment': (edMode != 'view') & (edMode != 'fillForms') & (edMode != 'embedded') & (edMode != "blockcontent"),
'comment': (edMode != 'view') & (edMode != 'fillForms') & (edMode != 'embedded') \
& (edMode != "blockcontent"),
'copy': 'copy' not in user.deniedPermissions,
'download': 'download' not in user.deniedPermissions,
'edit': canEdit & ((edMode == 'edit') | (edMode == 'view') | (edMode == 'filter') | (edMode == "blockcontent")),
'edit': canEdit & ((edMode == 'edit') | (edMode == 'view') | (edMode == 'filter') \
| (edMode == "blockcontent")),
'print': 'print' not in user.deniedPermissions,
'fillForms': (edMode != 'view') & (edMode != 'comment') & (edMode != 'embedded') & (edMode != "blockcontent"),
'fillForms': (edMode != 'view') & (edMode != 'comment') & (edMode != 'embedded') \
& (edMode != "blockcontent"),
'modifyFilter': edMode != 'filter',
'modifyContentControl': edMode != "blockcontent",
'review': canEdit & ((edMode == 'edit') | (edMode == 'review')),
'chat': user.id !='uid-0',
'chat': user.id != 'uid-0',
'reviewGroups': user.reviewGroups,
'commentGroups': user.commentGroups,
'userInfoGroups': user.userInfoGroups,
'protect': 'protect' not in user.deniedPermissions
},
'referenceData' : {
'instanceId' : docManager.getServerUrl(False, request),
'fileKey' : json.dumps({'fileName' : filename, 'userAddress': request.META['REMOTE_ADDR']}) if user.id !='uid-0' else None
'referenceData': {
'instanceId': docManager.getServerUrl(False, request),
'fileKey': json.dumps({'fileName': filename,
'userAddress': request.META['REMOTE_ADDR']}) if user.id != 'uid-0' else None
}
},
'editorConfig': {
@ -260,31 +285,35 @@ def edit(request):
'lang': lang,
'callbackUrl': docManager.getCallbackUrl(filename, request), # absolute URL to the document storage service
'coEditing': {
"mode": "strict",
"change": False
}
if edMode == 'view' and user.id =='uid-0' else None,
'createUrl' : createUrl if user.id !='uid-0' else None,
'templates' : templates if user.templates else None,
"mode": "strict",
"change": False
}
if edMode == 'view' and user.id == 'uid-0' else None,
'createUrl': createUrl if user.id != 'uid-0' else None,
'templates': templates if user.templates else None,
'user': { # the user currently viewing or editing the document
'id': user.id if user.id !='uid-0' else None,
'id': user.id if user.id != 'uid-0' else None,
'name': user.name,
'group': user.group
},
'embedded': { # the parameters for the embedded document type
'saveUrl': directUrl, # the absolute URL that will allow the document to be saved onto the user personal computer
'embedUrl': directUrl, # the absolute URL to the document serving as a source file for the document embedded into the web page
# the absolute URL that will allow the document to be saved onto the user personal computer
'saveUrl': directUrl,
# the absolute URL to the document serving as a source file for the document embedded into the web page
'embedUrl': directUrl,
'shareUrl': directUrl, # the absolute URL that will allow other users to share this document
'toolbarDocked': 'top' # the place for the embedded viewer toolbar (top or bottom)
},
'customization': { # the parameters for the editor interface
'about': True, # the About section display
'comments': True,
'comments': True,
'feedback': True, # the Feedback & Support menu button display
'forcesave': False, # adds the request for the forced file saving to the callback handler
'submitForm': submitForm, # if the Submit form button is displayed or not
'goback': { # settings for the Open file location menu button and upper right corner button
'url': docManager.getServerUrl(False, request) # the absolute URL to the website address which will be opened when clicking the Open file location menu button
'goback': { # settings for the Open file location menu button and upper right corner button
# the absolute URL to the website address
# which will be opened when clicking the Open file location menu button
'url': docManager.getServerUrl(False, request)
}
}
}
@ -321,7 +350,7 @@ def edit(request):
}
# users data for mentions
usersForMentions = users.getUsersForMentions(user.id)
usersForMentions = users.getUsersForMentions(user.id)
if jwtManager.isEnabled(): # if the secret key to generate token exists
edConfig['token'] = jwtManager.encode(edConfig) # encode the edConfig object into a token
@ -329,21 +358,26 @@ def edit(request):
dataDocument['token'] = jwtManager.encode(dataDocument) # encode the dataDocument object into a token
dataSpreadsheet['token'] = jwtManager.encode(dataSpreadsheet) # encode the dataSpreadsheet object into a token
hist = historyManager.getHistoryObject(storagePath, filename, docKey, fileUri, isEnableDirectUrl, request) # get the document history
# get the document history
hist = historyManager.getHistoryObject(storagePath, filename, docKey, fileUri, isEnableDirectUrl, request)
context = { # the data that will be passed to the template
'cfg': json.dumps(edConfig), # the document config in json format
'history': json.dumps(hist['history']) if 'history' in hist else None, # the information about the current version
'historyData': json.dumps(hist['historyData']) if 'historyData' in hist else None, # the information about the previous document versions if they exist
# the information about the current version
'history': json.dumps(hist['history']) if 'history' in hist else None,
# the information about the previous document versions if they exist
'historyData': json.dumps(hist['historyData']) if 'historyData' in hist else None,
'fileType': fileType, # the file type of the document (text, spreadsheet or presentation)
'apiUrl': config_manager.document_server_api_url().geturl(), # the absolute URL to the api
'dataInsertImage': json.dumps(dataInsertImage)[1 : len(json.dumps(dataInsertImage)) - 1], # the image which will be inserted into the document
# the image which will be inserted into the document
'dataInsertImage': json.dumps(dataInsertImage)[1: len(json.dumps(dataInsertImage)) - 1],
'dataDocument': dataDocument, # document which will be compared with the current document
'dataSpreadsheet': json.dumps(dataSpreadsheet), # recipient data for mail merging
'usersForMentions': json.dumps(usersForMentions) if user.id !='uid-0' else None
'usersForMentions': json.dumps(usersForMentions) if user.id != 'uid-0' else None
}
return render(request, 'editor.html', context) # execute the "editor.html" template with context data
# track the document changes
def track(request):
response = {}
@ -352,11 +386,12 @@ def track(request):
body = trackManager.readBody(request) # read request body
status = body['status'] # and get status from it
if (status == 1): # editing
if status == 1: # editing
if (body['actions'] and body['actions'][0]['type'] == 0): # finished edit
user = body['actions'][0]['userid'] # the user who finished editing
if (not user in body['users']):
trackManager.commandRequest('forcesave', body['key']) # create a command request with the forcasave method
if user not in body['users']:
# create a command request with the forcasave method
trackManager.commandRequest('forcesave', body['key'])
filename = fileUtils.getFileName(request.GET['filename'])
usAddr = request.GET['userAddress']
@ -367,12 +402,15 @@ def track(request):
trackManager.processForceSave(body, filename, usAddr)
except Exception as e:
response.setdefault("error", 1) # set the default error value as 1 (document key is missing or no document with such key could be found)
# set the default error value as 1 (document key is missing or no document with such key could be found)
response.setdefault("error", 1)
response.setdefault("message", str(e.args[0]))
response.setdefault('error', 0) # if no exceptions are raised, the default error value is 0 (no errors)
# the response status is 200 if the changes are saved successfully; otherwise, it is equal to 500
return HttpResponse(json.dumps(response), content_type='application/json', status=200 if response['error'] == 0 else 500)
return HttpResponse(json.dumps(response), content_type='application/json',
status=200 if response['error'] == 0 else 500)
# remove a file
def remove(request):
@ -385,6 +423,7 @@ def remove(request):
response.setdefault('success', True)
return HttpResponse(json.dumps(response), content_type='application/json')
# get file information
def files(request):
try:
@ -394,12 +433,14 @@ def files(request):
response.setdefault('error', e.args[0])
return HttpResponse(json.dumps(response), content_type='application/json')
# download a csv file
def csv(request):
def csv():
filePath = os.path.join('assets', 'document-templates', 'sample', "csv.csv")
response = docManager.download(filePath)
return response
# download a file
def download(request):
try:
@ -407,21 +448,22 @@ def download(request):
userAddress = request.GET.get('userAddress')
isEmbedded = request.GET.get('dmode')
if (jwtManager.isEnabled() and isEmbedded == None and userAddress and jwtManager.useForRequest()):
if (jwtManager.isEnabled() and isEmbedded is None and userAddress and jwtManager.useForRequest()):
token = request.headers.get(config_manager.jwt_header())
if token:
token = token[len('Bearer '):]
try:
body = jwtManager.decode(token)
jwtManager.decode(token)
except Exception:
return HttpResponse('JWT validation failed', status=403)
if (userAddress == None):
if userAddress is None:
userAddress = request
filePath = docManager.getForcesavePath(fileName, userAddress, False) # get the path to the forcesaved file version
if (filePath == ""):
# get the path to the forcesaved file version
filePath = docManager.getForcesavePath(fileName, userAddress, False)
if filePath == "":
filePath = docManager.getStoragePath(fileName, userAddress) # get file from the storage directory
response = docManager.download(filePath) # download this file
return response
@ -430,6 +472,7 @@ def download(request):
response.setdefault('error', 'File not found')
return HttpResponse(json.dumps(response), content_type='application/json')
# download a history file
def downloadhistory(request):
try:
@ -439,12 +482,12 @@ def downloadhistory(request):
version = fileUtils.getFileName(request.GET['ver'])
isEmbedded = request.GET.get('dmode')
if (jwtManager.isEnabled() and isEmbedded == None and jwtManager.useForRequest()):
if (jwtManager.isEnabled() and isEmbedded is None and jwtManager.useForRequest()):
token = request.headers.get(config_manager.jwt_header())
if token:
token = token[len('Bearer '):]
try:
body = jwtManager.decode(token)
jwtManager.decode(token)
except Exception:
return HttpResponse('JWT validation failed', status=403)
else:
@ -459,6 +502,7 @@ def downloadhistory(request):
response.setdefault('error', 'File not found')
return HttpResponse(json.dumps(response), content_type='application/json', status=404)
# referenceData
def reference(request):
response = {}
@ -479,38 +523,39 @@ def reference(request):
userAddress = fileKey['userAddress']
if userAddress == request.META['REMOTE_ADDR']:
fileName = fileKey['fileName']
if fileName is None:
try:
path = fileUtils.getFileName(body['path'])
if os.path.exists(docManager.getStoragePath(path,request)):
fileName = path
if os.path.exists(docManager.getStoragePath(path, request)):
fileName = path
except KeyError:
response.setdefault('error', 'Path not found')
return HttpResponse(json.dumps(response), content_type='application/json', status=404)
if fileName is None:
response.setdefault('error', 'File not found')
return HttpResponse(json.dumps(response), content_type='application/json', status=404)
data = {
'fileType' : fileUtils.getFileExt(fileName).replace('.', ''),
'key': docManager.generateFileKey(fileName,request),
'url' : docManager.getDownloadUrl(fileName, request),
'directUrl' : docManager.getDownloadUrl(fileName, request, False) if body["directUrl"] else None,
'referenceData' : {
'instanceId' : docManager.getServerUrl(False, request),
'fileKey' : json.dumps({'fileName' : fileName, 'userAddress': request.META['REMOTE_ADDR']})
'fileType': fileUtils.getFileExt(fileName).replace('.', ''),
'key': docManager.generateFileKey(fileName, request),
'url': docManager.getDownloadUrl(fileName, request),
'directUrl': docManager.getDownloadUrl(fileName, request, False) if body["directUrl"] else None,
'referenceData': {
'instanceId': docManager.getServerUrl(False, request),
'fileKey': json.dumps({'fileName': fileName, 'userAddress': request.META['REMOTE_ADDR']})
},
'path' : fileName,
'link' : docManager.getServerUrl(False, request) + '/edit?filename=' + fileName
'path': fileName,
'link': docManager.getServerUrl(False, request) + '/edit?filename=' + fileName
}
if (jwtManager.isEnabled()):
if jwtManager.isEnabled():
data['token'] = jwtManager.encode(data)
return HttpResponse(json.dumps(data), content_type='application/json')
@http.PUT()
def restore(request: HttpRequest) -> HttpResponse:
try:

View File

@ -16,8 +16,6 @@
"""
import re
import sys
import json
from django.shortcuts import render
@ -30,11 +28,13 @@ from src.utils import docManager
config_manager = ConfigurationManager()
format_manager = FormatManager()
def getDirectUrlParam(request):
if ('directUrl' in request.GET):
if 'directUrl' in request.GET:
return request.GET['directUrl'].lower() in ("true")
else:
return False;
return False
def default(request): # default parameters that will be passed to the template
context = {
@ -47,4 +47,5 @@ def default(request): # default parameters that will be passed to the template
'fillExt': json.dumps(format_manager.fillable_extensions()),
'directUrl': str(getDirectUrlParam(request)).lower
}
return render(request, 'index.html', context) # execute the "index.html" template with context data and return http response in json format
# execute the "index.html" template with context data and return http response in json format
return render(request, 'index.html', context)