# -*- 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///'], 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//'], type='jsonrpc', 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)