110 lines
5.2 KiB
Python
110 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
import base64
|
|
import io
|
|
import logging
|
|
|
|
from odoo import http
|
|
from odoo.http import request
|
|
from odoo.addons.sign.controllers.main import Sign
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class SignController(Sign):
|
|
|
|
def get_document_qweb_context(self, sign_request_id, token, **post):
|
|
context = super().get_document_qweb_context(sign_request_id, token, **post)
|
|
|
|
# Optimization: Replace base64 image content with URL to prevent massive response size
|
|
# and "ERR_QUIC_PROTOCOL_ERROR" in production.
|
|
if 'item_values' in context and 'sign_items' in context:
|
|
image_items = context['sign_items'].filtered(lambda i: i.type_id.item_type == 'image')
|
|
for item in image_items:
|
|
if item.id in context['item_values']:
|
|
# Reset the value to a URL
|
|
# We use a special prefix to identify this as a placeholder URL
|
|
url = f"/sign/image/{sign_request_id}/{token}/{item.id}"
|
|
context['item_values'][item.id] = url
|
|
_logger.info(f"Replaced base64 content for item {item.id} with URL {url}")
|
|
|
|
return context
|
|
|
|
@http.route(['/sign/image/<int:sign_request_id>/<token>/<int:item_id>'], type='http', auth='public')
|
|
def get_sign_image(self, sign_request_id, token, item_id):
|
|
sign_request = request.env['sign.request'].sudo().browse(sign_request_id)
|
|
if not sign_request.exists() or sign_request.access_token != token:
|
|
return request.not_found()
|
|
|
|
# Check access to the specific item value
|
|
# We need to find the value record for this request and item
|
|
# The logic mimics get_document_qweb_context's search for values
|
|
|
|
# Check if the user is the relevant signer (via token) or if the document is completed
|
|
current_request_item = sign_request.request_item_ids.filtered(lambda r: r.access_token == token)
|
|
|
|
# Allow access if:
|
|
# 1. User has the token for the request (already checked above with sign_request.access_token)
|
|
# 2. But sign_request.access_token is the SHARED token (for everyone?)
|
|
# - No, sign_request.access_token is usually for the 'shared' link.
|
|
# - Signers have individual tokens on request_item.
|
|
|
|
# Let's strengthen the check:
|
|
# If token matches sign_request.access_token -> Public/Shared view.
|
|
# If token matches request_item.access_token -> Specific signer.
|
|
|
|
is_shared_token = (sign_request.access_token == token)
|
|
signer_access = sign_request.request_item_ids.filtered(lambda r: r.access_token == token)
|
|
|
|
if not is_shared_token and not signer_access:
|
|
return request.not_found()
|
|
|
|
# Fetch value
|
|
# We look for value associated with this request and item.
|
|
# Logic from get_document_qweb_context:
|
|
# sr_values = http.request.env['sign.request.item.value'].sudo().search([
|
|
# ('sign_request_id', '=', sign_request.id),
|
|
# '|',
|
|
# ('sign_request_item_id', '=', current_request_item.id),
|
|
# ('sign_request_item_id.state', '=', 'completed')
|
|
# ])
|
|
|
|
domain = [('sign_request_id', '=', sign_request.id), ('sign_item_id', '=', item_id)]
|
|
|
|
# If accessing via specific signer token, likely we can see our own values or completed ones.
|
|
# If shared token, we likely see completed ones.
|
|
# To be safe and consistent with the view, we fetch the value and assume if it exists in the system
|
|
# linked to this request, and the user has valid access to the *Request*, they can see it
|
|
# (images are generally public within the context of the document signatures).
|
|
|
|
value_record = request.env['sign.request.item.value'].sudo().search(domain, limit=1)
|
|
|
|
if not value_record or not value_record.value:
|
|
return request.not_found()
|
|
|
|
try:
|
|
image_data = base64.b64decode(value_record.value)
|
|
return request.make_response(
|
|
image_data,
|
|
headers=[
|
|
('Content-Type', 'image/png'), # Assuming PNG or relying on browser sniffing.
|
|
# Realistically could be JPEG, but browser handles it.
|
|
('Cache-Control', 'max-age=3600, public'),
|
|
]
|
|
)
|
|
except Exception:
|
|
return request.not_found()
|
|
|
|
@http.route(['/sign/sign/<int:request_id>/<token>'], type='json', auth='public')
|
|
def sign_document(self, request_id, token, signature=None, items=None, **kwargs):
|
|
# Intercept items to prevent saving the URL as value
|
|
if items:
|
|
keys_to_remove = []
|
|
for key, value in items.items():
|
|
if isinstance(value, str) and value.startswith('/sign/image/'):
|
|
keys_to_remove.append(key)
|
|
|
|
for key in keys_to_remove:
|
|
_logger.info(f"Ignoring URL value for item {key} to preserve existing binary data")
|
|
del items[key]
|
|
|
|
return super().sign_document(request_id, token, signature=signature, items=items, **kwargs)
|