From 9e886336846a8a3eae6652a1ec640a1dde1969a3 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Mon, 18 Dec 2023 21:19:40 +0100 Subject: [PATCH] [IMP] sign_oca: Set Encrypted data --- sign_oca/README.rst | 2 +- sign_oca/__manifest__.py | 1 + sign_oca/controllers/main.py | 21 +++- sign_oca/models/__init__.py | 1 + sign_oca/models/sign_oca_certificate.py | 19 ++++ sign_oca/models/sign_oca_request.py | 22 ++++- sign_oca/security/ir.model.access.csv | 2 + sign_oca/static/description/index.html | 2 +- .../sign_oca_pdf/sign_oca_pdf.esm.js | 98 +++++++++++++++++-- .../sign_oca_pdf_portal.esm.js | 16 ++- sign_oca/views/sign_oca_certificate.xml | 55 +++++++++++ 11 files changed, 220 insertions(+), 19 deletions(-) create mode 100644 sign_oca/models/sign_oca_certificate.py create mode 100644 sign_oca/views/sign_oca_certificate.xml diff --git a/sign_oca/README.rst b/sign_oca/README.rst index 86cc9687..2be6ee20 100644 --- a/sign_oca/README.rst +++ b/sign_oca/README.rst @@ -7,7 +7,7 @@ Sign Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:d72fccf1681d5858e60f2081a239068805616cb501be2c56b04487069c1b0430 + !! source digest: sha256:1ca14ee64b072e4143fd7d0df007c634bba212835836fbbac28e88ae1a3c1d5b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/sign_oca/__manifest__.py b/sign_oca/__manifest__.py index 4c890cc7..484a367e 100644 --- a/sign_oca/__manifest__.py +++ b/sign_oca/__manifest__.py @@ -21,6 +21,7 @@ "views/sign_oca_field.xml", "views/sign_oca_role.xml", "views/sign_oca_template.xml", + "views/sign_oca_certificate.xml", "templates/assets.xml", ], "demo": [ diff --git a/sign_oca/controllers/main.py b/sign_oca/controllers/main.py index 920940f5..f9d4729d 100644 --- a/sign_oca/controllers/main.py +++ b/sign_oca/controllers/main.py @@ -73,6 +73,21 @@ def get_sign_oca_content_access(self, signer_id, access_token): data = io.BytesIO(base64.standard_b64decode(signer_sudo.request_id.data)) return http.send_file(data, filename=signer_sudo.request_id.name) + @http.route( + ["/sign_oca/certificate//"], + type="json", + auth="public", + website=True, + ) + def get_sign_oca_certificate(self, signer_id, access_token): + try: + signer_sudo = self._document_check_access( + "sign.oca.request.signer", signer_id, access_token + ) + except (AccessError, MissingError): + return request.redirect("/my") + return signer_sudo.sign_certificate_id.data + @http.route( ["/sign_oca/info//"], type="json", @@ -94,12 +109,14 @@ def get_sign_oca_info_access(self, signer_id, access_token): auth="public", website=True, ) - def get_sign_oca_sign_access(self, signer_id, access_token, items): + def get_sign_oca_sign_access(self, signer_id, access_token, items, encrypted_data): try: signer_sudo = self._document_check_access( "sign.oca.request.signer", signer_id, access_token ) except (AccessError, MissingError): return request.redirect("/my") - signer_sudo.action_sign(items, access_token=access_token) + signer_sudo.action_sign( + items, encrypted_data=encrypted_data, access_token=access_token + ) return True diff --git a/sign_oca/models/__init__.py b/sign_oca/models/__init__.py index 0ae418e9..8d0246c0 100644 --- a/sign_oca/models/__init__.py +++ b/sign_oca/models/__init__.py @@ -2,3 +2,4 @@ from . import sign_oca_role from . import sign_oca_field from . import sign_oca_request +from . import sign_oca_certificate diff --git a/sign_oca/models/sign_oca_certificate.py b/sign_oca/models/sign_oca_certificate.py new file mode 100644 index 00000000..9dae88d2 --- /dev/null +++ b/sign_oca/models/sign_oca_certificate.py @@ -0,0 +1,19 @@ +# Copyright 2023 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class SignOcaCertificate(models.Model): + """ + This certificate will allow us to encrypt sensitive data + We will not be able to decrypt it without the private key + """ + + _name = "sign.oca.certificate" + _description = "Sign Public Certificate" + _order = "id desc" + + name = fields.Char(required=True) + data = fields.Char() + active = fields.Boolean(default=True) diff --git a/sign_oca/models/sign_oca_request.py b/sign_oca/models/sign_oca_request.py index d67a285a..324ad4f8 100644 --- a/sign_oca/models/sign_oca_request.py +++ b/sign_oca/models/sign_oca_request.py @@ -263,13 +263,25 @@ class SignOcaRequestSigner(models.Model): _inherit = "portal.mixin" _description = "Sign Request Value" - data = fields.Binary(related="request_id.data") + data = fields.Binary(related="request_id.data", copy=False) request_id = fields.Many2one("sign.oca.request", required=True, ondelete="cascade") partner_name = fields.Char(related="partner_id.name") partner_id = fields.Many2one("res.partner", required=True, ondelete="restrict") role_id = fields.Many2one("sign.oca.role", required=True, ondelete="restrict") - signed_on = fields.Datetime(readonly=True) - signature_hash = fields.Char(readonly=True) + signed_on = fields.Datetime(readonly=True, copy=False) + signature_hash = fields.Char(readonly=True, copy=False) + sign_certificate_id = fields.Many2one( + "sign.oca.certificate", + default=lambda r: r._get_sign_certificate(), + readonly=True, + copy=False, + ) + sensitive_data = fields.Binary(readonly=True, copy=False) + encrypted_data = fields.Json() + + @api.model + def _get_sign_certificate(self): + return self.env["sign.oca.certificate"].search([], limit=1) def _compute_access_url(self): result = super()._compute_access_url() @@ -288,6 +300,7 @@ def get_info(self, access_token=False): "name": self.request_id.template_id.name, "items": self.request_id.signatory_data, "to_sign": self.request_id.to_sign, + "certificate_id": self.sign_certificate_id.id, "partner": { "id": self.env.user.partner_id.id, "name": self.env.user.partner_id.name, @@ -296,7 +309,7 @@ def get_info(self, access_token=False): }, } - def action_sign(self, items, access_token=False): + def action_sign(self, items, encrypted_data=False, access_token=False): self.ensure_one() if self.signed_on: raise ValidationError( @@ -305,6 +318,7 @@ def action_sign(self, items, access_token=False): if self.request_id.state != "sent": raise ValidationError(_("Request cannot be signed")) self.signed_on = fields.Datetime.now() + self.encrypted_data = encrypted_data # current_hash = self.request_id.current_hash signatory_data = self.request_id.signatory_data diff --git a/sign_oca/security/ir.model.access.csv b/sign_oca/security/ir.model.access.csv index 57569719..c25bb8e4 100644 --- a/sign_oca/security/ir.model.access.csv +++ b/sign_oca/security/ir.model.access.csv @@ -8,3 +8,5 @@ edit_sign_request_signer,edit_sign_field,model_sign_oca_request_signer,sign_oca_ edit_sign_generate,edit_sign_field,model_sign_oca_template_generate,sign_oca_group_user,1,1,1,1 edit_sign_generate_signer,edit_sign_field,model_sign_oca_template_generate_signer,sign_oca_group_user,1,1,1,1 access_sign_request_log,access_sign_request_log,model_sign_oca_request_log,sign_oca_group_user,1,0,0,0 +access_sign_certificate,access_sign_certificate,model_sign_oca_certificate,sign_oca_group_user,1,0,0,0 +edit_sign_certificate,edit_sign_certificate,model_sign_oca_certificate,base.group_system,1,1,1,0 diff --git a/sign_oca/static/description/index.html b/sign_oca/static/description/index.html index 4f6cfa1b..4a40c4b8 100644 --- a/sign_oca/static/description/index.html +++ b/sign_oca/static/description/index.html @@ -367,7 +367,7 @@

Sign Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:d72fccf1681d5858e60f2081a239068805616cb501be2c56b04487069c1b0430 +!! source digest: sha256:1ca14ee64b072e4143fd7d0df007c634bba212835836fbbac28e88ae1a3c1d5b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/sign Translate me on Weblate Try me on Runboat

This module allows to create documents for signature inside Odoo using OWL.

diff --git a/sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.esm.js b/sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.esm.js index f942f626..31e5f438 100644 --- a/sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.esm.js +++ b/sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.esm.js @@ -8,6 +8,7 @@ export default class SignOcaPdf extends SignOcaPdfCommon { setup() { super.setup(...arguments); this.to_sign = false; + this.sensitiveData = {}; } async willStart() { await super.willStart(...arguments); @@ -21,6 +22,54 @@ export default class SignOcaPdf extends SignOcaPdfCommon { }); this.to_sign = this.to_sign_update; } + async _encryptSensitiveData(publicKeyBase64) { + const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => + c.charCodeAt(0) + ); + var importedPublicKey = await crypto.subtle.importKey( + "spki", + publicKeyBytes.buffer, + {name: "ECDH", namedCurve: "P-256"}, + true, + [] + ); + const privateKey = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey"] + ); + const sharedKey = await crypto.subtle.deriveKey( + { + name: "ECDH", + public: importedPublicKey, + }, + privateKey.privateKey, + { + name: "AES-CBC", + length: 256, + }, + true, + ["encrypt", "decrypt"] + ); + const iv = crypto.getRandomValues(new Uint8Array(16)); + const encryptedBuffer = await crypto.subtle.encrypt( + { + name: "AES-CBC", + iv: iv, + }, + sharedKey, + new TextEncoder().encode(JSON.stringify(this.sensitiveData)) + ); + const publicKey = await crypto.subtle.exportKey("spki", privateKey.publicKey); + this.encryptedData = { + iv: btoa(String.fromCharCode(...iv)), + data: btoa(String.fromCharCode(...new Uint8Array(encryptedBuffer))), + public: btoa(String.fromCharCode(...new Uint8Array(publicKey))), + }; + } renderButtons(to_sign) { var $buttons = $( core.qweb.render("oca_sign_oca.SignatureButtons", { @@ -28,15 +77,46 @@ export default class SignOcaPdf extends SignOcaPdfCommon { }) ); $buttons.on("click.o_sign_oca_button_sign", () => { - this.env.services - .rpc({ - model: this.props.model, - method: "action_sign", - args: [[this.props.res_id], this.info.items], - }) - .then(() => { - this.props.trigger("history_back"); - }); + // TODO: Add encryption here + var todoFirst = []; + this.encryptedData = {}; + if ( + Object.keys(this.sensitiveData).length > 0 && + this.info.certificate_id + ) { + todoFirst.push( + new Promise((resolve) => { + this.env.services + .rpc({ + model: "sign.oca.certificate", + method: "read", + args: [[this.info.certificate_id], ["data"]], + }) + .then((public_certificate_info) => { + this._encryptSensitiveData( + public_certificate_info[0].data + ).then(() => { + resolve(); + }); + }); + }) + ); + } + Promise.all(todoFirst).then(() => { + this.env.services + .rpc({ + model: this.props.model, + method: "action_sign", + args: [ + [this.props.res_id], + this.info.items, + this.encryptedData, + ], + }) + .then(() => { + this.props.trigger("history_back"); + }); + }); }); return $buttons; } diff --git a/sign_oca/static/src/components/sign_oca_pdf_portal/sign_oca_pdf_portal.esm.js b/sign_oca/static/src/components/sign_oca_pdf_portal/sign_oca_pdf_portal.esm.js index 29b4d546..28f012dc 100644 --- a/sign_oca/static/src/components/sign_oca_pdf_portal/sign_oca_pdf_portal.esm.js +++ b/sign_oca/static/src/components/sign_oca_pdf_portal/sign_oca_pdf_portal.esm.js @@ -12,6 +12,7 @@ export class SignOcaPdfPortal extends SignOcaPdf { setup() { super.setup(...arguments); this.signOcaFooter = useRef("sign_oca_footer"); + this.sensitiveData = {}; } async willStart() { this.info = await this.env.services.rpc({ @@ -40,14 +41,25 @@ export class SignOcaPdfPortal extends SignOcaPdf { super.postIframeFields(...arguments); this.checkFilledAll(); } - _onClickSign() { + async _onClickSign() { + this.encryptedData = false; + if (Object.keys(this.sensitiveData).length > 0 && this.info.certificate_id) { + const public_certificate_info = await this.env.services.rpc({ + route: + "/sign_oca/certificate/" + + this.props.signer_id + + "/" + + this.props.access_token, + }); + await this._encryptSensitiveData(public_certificate_info); + } this.env.services.rpc({ route: "/sign_oca/sign/" + this.props.signer_id + "/" + this.props.access_token, - params: {items: this.info.items}, + params: {items: this.info.items, encrypted_data: this.encryptedData}, }); } } diff --git a/sign_oca/views/sign_oca_certificate.xml b/sign_oca/views/sign_oca_certificate.xml new file mode 100644 index 00000000..fdf3fb5e --- /dev/null +++ b/sign_oca/views/sign_oca_certificate.xml @@ -0,0 +1,55 @@ + + + + + + sign.oca.certificate + +
+
+
+ + + + + + +
+
+
+ + + sign.oca.certificate + + + + + + + + + sign.oca.certificate + + + + + + + + + Sign Oca Certificate + sign.oca.certificate + tree,form + [] + {} + + + + Sign Oca Certificate + + + + + +