304 lines
13 KiB
Python
304 lines
13 KiB
Python
#
|
|
# (c) Copyright Ascensio System SIA 2024
|
|
#
|
|
import base64
|
|
import json
|
|
import logging
|
|
import re
|
|
from mimetypes import guess_type
|
|
from urllib.request import urlopen
|
|
|
|
import markupsafe
|
|
import requests
|
|
from werkzeug.exceptions import Forbidden
|
|
|
|
from odoo import http
|
|
from odoo.exceptions import AccessError
|
|
from odoo.http import request
|
|
from odoo.tools.translate import _
|
|
|
|
from odoo.addons.documents.controllers.documents import ShareRoute
|
|
from odoo.addons.onlyoffice_odoo.controllers.controllers import Onlyoffice_Connector
|
|
from odoo.addons.onlyoffice_odoo.utils import config_utils, file_utils, jwt_utils, url_utils
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
_mobile_regex = r"android|avantgo|playbook|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino" # noqa: E501
|
|
|
|
|
|
class OnlyofficeDocuments_Connector(http.Controller):
|
|
@http.route("/onlyoffice/documents/file/create", auth="user", methods=["POST"], type="json")
|
|
def post_file_create(self, folder_id, supported_format, title, url=None):
|
|
result = {"error": None, "file_id": None, "document_id": None}
|
|
|
|
try:
|
|
_logger.info(f"Getting new file template {request.env.user.lang} {supported_format}")
|
|
|
|
if url:
|
|
response = requests.get(url, stream=True, timeout=30)
|
|
response.raise_for_status()
|
|
file_data = response.content
|
|
else:
|
|
file_data = file_utils.get_default_file_template(request.env.user.lang, supported_format)
|
|
|
|
data = {
|
|
"name": title + "." + supported_format,
|
|
"mimetype": file_utils.get_mime_by_ext(supported_format),
|
|
"raw": file_data,
|
|
"folder_id": int(folder_id),
|
|
}
|
|
|
|
document = request.env["documents.document"].create(data)
|
|
request.env["onlyoffice.odoo.documents.access"].create(
|
|
{
|
|
"document_id": document.id,
|
|
"internal_users": "none",
|
|
"link_access": "none",
|
|
}
|
|
)
|
|
request.env["onlyoffice.odoo.documents.access.user"].create(
|
|
{
|
|
"document_id": document.id,
|
|
"user_id": request.env.user.partner_id.id,
|
|
"role": "edit",
|
|
}
|
|
)
|
|
result["file_id"] = document.attachment_id.id
|
|
result["document_id"] = document.id
|
|
|
|
except Exception as ex:
|
|
_logger.exception(f"Failed to create document {str(ex)}")
|
|
result["error"] = _("Failed to create document")
|
|
|
|
return json.dumps(result)
|
|
|
|
|
|
class OnlyofficeDocuments_Inherited_Connector(Onlyoffice_Connector):
|
|
@http.route(["/onlyoffice/documents/share/<access_token>/"], type="http", auth="public")
|
|
def render_shared_document_editor(self, access_token=None):
|
|
try:
|
|
document = ShareRoute._from_access_token(access_token, skip_log=True)
|
|
|
|
if not document or not document.exists():
|
|
raise request.not_found()
|
|
|
|
return request.render(
|
|
"onlyoffice_odoo.onlyoffice_editor", self.prepare_share_editor(document, access_token)
|
|
)
|
|
|
|
except Exception:
|
|
_logger.error("Ffailed to open shared document")
|
|
|
|
return request.not_found()
|
|
|
|
@http.route("/onlyoffice/editor/document/<int:document_id>", auth="public", type="http", website=True)
|
|
def render_document_editor(self, document_id, access_token=None):
|
|
return request.render(
|
|
"onlyoffice_odoo.onlyoffice_editor", self.prepare_document_editor(document_id, access_token)
|
|
)
|
|
|
|
def prepare_document_editor(self, document_id, access_token):
|
|
document = request.env["documents.document"].browse(int(document_id))
|
|
if document.is_locked and document.lock_uid.id != request.env.user.id:
|
|
_logger.error("Document is locked by another user")
|
|
raise Forbidden()
|
|
try:
|
|
document.check_access_rule("read")
|
|
except AccessError:
|
|
_logger.error("User has no read access rights to open this document")
|
|
raise Forbidden() # noqa: B904
|
|
|
|
attachment = self.get_attachment(document.attachment_id.id)
|
|
if not attachment:
|
|
_logger.error("Current document has no attachments")
|
|
raise Forbidden() # noqa: B904
|
|
|
|
try:
|
|
document.check_access_rule("write")
|
|
return self.prepare_editor_values(attachment, access_token, True)
|
|
except AccessError:
|
|
_logger.debug("Current user has no write access")
|
|
return self.prepare_editor_values(attachment, access_token, False)
|
|
|
|
def prepare_share_editor(self, document, access_token):
|
|
role = None
|
|
access = (
|
|
request.env["onlyoffice.odoo.documents.access"].sudo().search([("document_id", "=", document.id)], limit=1)
|
|
)
|
|
if access:
|
|
if access.link_access == "none":
|
|
raise AccessError(_("User has no read access rights to open this document"))
|
|
else:
|
|
role = access.link_access
|
|
|
|
attachment = self.get_attachment(document.attachment_id.id)
|
|
data = attachment.sudo().read(["id", "checksum", "public", "name", "access_token"])[0]
|
|
key = str(data["id"]) + str(data["checksum"])
|
|
docserver_url = config_utils.get_doc_server_public_url(request.env)
|
|
odoo_url = config_utils.get_base_or_odoo_url(request.env)
|
|
|
|
filename = self.filter_xss(data["name"])
|
|
access_token = access_token.decode("utf-8") if isinstance(access_token, bytes) else access_token
|
|
document_type = file_utils.get_file_type(filename)
|
|
is_mobile = bool(re.search(_mobile_regex, request.httprequest.headers.get("User-Agent"), re.IGNORECASE))
|
|
|
|
root_config = {
|
|
"width": "100%",
|
|
"height": "100%",
|
|
"type": "mobile" if is_mobile else "desktop",
|
|
"documentType": document_type,
|
|
"document": {
|
|
"title": filename,
|
|
"url": odoo_url + "documents/content/" + access_token,
|
|
"fileType": file_utils.get_file_ext(filename),
|
|
"key": key,
|
|
"permissions": {"edit": False},
|
|
},
|
|
"editorConfig": {
|
|
"mode": "view",
|
|
"lang": request.env.user.lang,
|
|
"user": {"id": str(request.env.user.id), "name": request.env.user.name},
|
|
"customization": {},
|
|
},
|
|
}
|
|
|
|
if not role or role == "view":
|
|
root_config["editorConfig"]["mode"] = "view"
|
|
root_config["document"]["permissions"]["edit"] = False
|
|
elif role == "commenter":
|
|
root_config["editorConfig"]["mode"] = "edit"
|
|
root_config["document"]["permissions"]["edit"] = False
|
|
root_config["document"]["permissions"]["comment"] = True
|
|
elif role == "reviewer":
|
|
root_config["editorConfig"]["mode"] = "edit"
|
|
root_config["document"]["permissions"]["edit"] = False
|
|
root_config["document"]["permissions"]["review"] = True
|
|
elif role == "edit":
|
|
root_config["editorConfig"]["mode"] = "edit"
|
|
root_config["document"]["permissions"]["edit"] = True
|
|
elif role == "form_filling":
|
|
root_config["editorConfig"]["mode"] = "edit"
|
|
root_config["document"]["permissions"]["edit"] = False
|
|
root_config["document"]["permissions"]["fillForms"] = True
|
|
elif role == "custom_filter":
|
|
root_config["editorConfig"]["mode"] = "edit"
|
|
root_config["document"]["permissions"]["edit"] = True
|
|
root_config["document"]["permissions"]["modifyFilter"] = False
|
|
|
|
if role and role != "view":
|
|
public_user = request.env.ref("base.public_user")
|
|
security_token = jwt_utils.encode_payload(
|
|
request.env, {"id": public_user.id}, config_utils.get_internal_jwt_secret(request.env)
|
|
)
|
|
security_token = security_token.decode("utf-8") if isinstance(security_token, bytes) else security_token
|
|
root_config["editorConfig"]["callbackUrl"] = (
|
|
odoo_url + "onlyoffice/documents/share/callback/" + access_token + "/" + security_token
|
|
)
|
|
|
|
if jwt_utils.is_jwt_enabled(request.env):
|
|
root_config["token"] = jwt_utils.encode_payload(request.env, root_config)
|
|
|
|
return {
|
|
"docTitle": filename,
|
|
"docIcon": f"/onlyoffice_odoo/static/description/editor_icons/{document_type}.ico",
|
|
"docApiJS": docserver_url + "web-apps/apps/api/documents/api.js",
|
|
"editorConfig": markupsafe.Markup(json.dumps(root_config)),
|
|
}
|
|
|
|
@http.route(
|
|
"/onlyoffice/documents/share/callback/<access_token>/<oo_security_token>",
|
|
auth="public",
|
|
methods=["POST"],
|
|
type="http",
|
|
csrf=False,
|
|
)
|
|
def share_callback(self, access_token, oo_security_token):
|
|
response_json = {"error": 0}
|
|
|
|
try:
|
|
body = request.get_json_data()
|
|
user = self.get_user_from_token(oo_security_token)
|
|
document = ShareRoute._from_access_token(access_token, skip_log=True)
|
|
|
|
if not document or not document.exists():
|
|
raise request.not_found()
|
|
|
|
access = (
|
|
request.env["onlyoffice.odoo.documents.access"]
|
|
.sudo()
|
|
.search([("document_id", "=", document.id)], limit=1)
|
|
)
|
|
if access:
|
|
if access.link_access == "view":
|
|
raise Exception("No access rights to overwrite this document for access via share link")
|
|
else:
|
|
raise Exception("No access rights to overwrite this document for access via share link")
|
|
|
|
attachment = request.env["ir.attachment"].sudo().browse([document.attachment_id.id]).exists().ensure_one()
|
|
|
|
if jwt_utils.is_jwt_enabled(request.env):
|
|
token = body.get("token")
|
|
|
|
if not token:
|
|
token = request.httprequest.headers.get(config_utils.get_jwt_header(request.env))
|
|
if token:
|
|
token = token[len("Bearer ") :]
|
|
|
|
if not token:
|
|
raise Exception("expected JWT")
|
|
|
|
body = jwt_utils.decode_token(request.env, token)
|
|
if body.get("payload"):
|
|
body = body["payload"]
|
|
|
|
status = body["status"]
|
|
|
|
if (status == 2) | (status == 3): # mustsave, corrupted
|
|
file_url = url_utils.replace_public_url_to_internal(request.env, body.get("url"))
|
|
datas = base64.encodebytes(urlopen(file_url, timeout=120).read())
|
|
document = request.env["documents.document"].sudo().browse(int(attachment.res_id))
|
|
document.with_user(user).sudo().write(
|
|
{
|
|
"name": attachment.name,
|
|
"datas": datas,
|
|
"mimetype": guess_type(file_url)[0],
|
|
}
|
|
)
|
|
document.sudo().message_post(body=_("Document edited by %(user)s", user=user.name))
|
|
|
|
except Exception as ex:
|
|
response_json["error"] = 1
|
|
response_json["message"] = http.serialize_exception(ex)
|
|
|
|
return request.make_response(
|
|
data=json.dumps(response_json),
|
|
status=500 if response_json["error"] == 1 else 200,
|
|
headers=[("Content-Type", "application/json")],
|
|
)
|
|
|
|
|
|
class OnlyOfficeShareRoute(ShareRoute):
|
|
@http.route("/documents/<access_token>", type="http", auth="public")
|
|
def documents_home(self, access_token):
|
|
response = super(OnlyOfficeShareRoute, self).documents_home(access_token) # noqa: UP008
|
|
|
|
document_sudo = self._from_access_token(access_token)
|
|
|
|
if not request.env.user._is_public() or not hasattr(response, "qcontext"):
|
|
return
|
|
|
|
qcontext = response.qcontext
|
|
|
|
if document_sudo.type == "binary" and document_sudo.attachment_id:
|
|
can_view = file_utils.can_view(document_sudo.name)
|
|
if can_view:
|
|
qcontext["onlyoffice_supported"] = True
|
|
|
|
if document_sudo.type == "folder":
|
|
data = []
|
|
sub_documents_sudo = ShareRoute._get_folder_children(document_sudo)
|
|
for document in sub_documents_sudo:
|
|
data.append({"document": document, "onlyoffice_supported": file_utils.can_view(document.name)})
|
|
qcontext["onlyoffice_supported"] = data
|
|
|
|
return response
|