Skip to content

Commit

Permalink
[IMP] sign_oca: Set Encrypted data
Browse files Browse the repository at this point in the history
  • Loading branch information
etobella committed Dec 20, 2023
1 parent c80c846 commit 16a3d35
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 19 deletions.
2 changes: 1 addition & 1 deletion sign_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Sign Oca
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3819d34d8d64adcd3de4eb6d8cf79cf6ab4cc2e45aeeb9c0146982d2d1f3d1e7
!! source digest: sha256:5cc3c0344233f220fb76e8c13472ce33e646f8e58514eeb567abec7b7e6e8004
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down
1 change: 1 addition & 0 deletions sign_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
21 changes: 19 additions & 2 deletions sign_oca/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<int:signer_id>/<string:access_token>"],
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/<int:signer_id>/<string:access_token>"],
type="json",
Expand All @@ -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
1 change: 1 addition & 0 deletions sign_oca/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from . import sign_oca_role
from . import sign_oca_field
from . import sign_oca_request
from . import sign_oca_certificate
19 changes: 19 additions & 0 deletions sign_oca/models/sign_oca_certificate.py
Original file line number Diff line number Diff line change
@@ -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)
22 changes: 18 additions & 4 deletions sign_oca/models/sign_oca_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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

Expand Down
2 changes: 2 additions & 0 deletions sign_oca/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion sign_oca/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ <h1 class="title">Sign Oca</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3819d34d8d64adcd3de4eb6d8cf79cf6ab4cc2e45aeeb9c0146982d2d1f3d1e7
!! source digest: sha256:5cc3c0344233f220fb76e8c13472ce33e646f8e58514eeb567abec7b7e6e8004
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sign/tree/16.0/sign_oca"><img alt="OCA/sign" src="https://img.shields.io/badge/github-OCA%2Fsign-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sign-16-0/sign-16-0-sign_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sign&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows to create documents for signature inside Odoo using OWL.</p>
Expand Down
98 changes: 89 additions & 9 deletions sign_oca/static/src/components/sign_oca_pdf/sign_oca_pdf.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -21,22 +22,101 @@ 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", {
to_sign: to_sign,
})
);
$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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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},
});
}
}
Expand Down
55 changes: 55 additions & 0 deletions sign_oca/views/sign_oca_certificate.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Dixmit
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>

<record model="ir.ui.view" id="sign_oca_certificate_form_view">
<field name="model">sign.oca.certificate</field>
<field name="arch" type="xml">
<form>
<header>
</header>
<sheet>
<group>
<field name="name" />
<field name="data" />
</group>
</sheet>
</form>
</field>
</record>

<record model="ir.ui.view" id="sign_oca_certificate_search_view">
<field name="model">sign.oca.certificate</field>
<field name="arch" type="xml">
<search>
<field name="name" />
</search>
</field>
</record>

<record model="ir.ui.view" id="sign_oca_certificate_tree_view">
<field name="model">sign.oca.certificate</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
</tree>
</field>
</record>

<record model="ir.actions.act_window" id="sign_oca_certificate_act_window">
<field name="name">Sign Oca Certificate</field> <!-- TODO -->
<field name="res_model">sign.oca.certificate</field>
<field name="view_mode">tree,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
</record>

<record model="ir.ui.menu" id="sign_oca_certificate_menu">
<field name="name">Sign Oca Certificate</field>
<field name="parent_id" ref="sign_oca_settings_menu" />
<field name="action" ref="sign_oca_certificate_act_window" />
<field name="sequence" eval="99" />
</record>

</odoo>

0 comments on commit 16a3d35

Please sign in to comment.