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 Aug 25, 2024
1 parent ed088e0 commit 8b4c57b
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 17 deletions.
1 change: 1 addition & 0 deletions sign_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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 @@ -71,6 +71,21 @@ def get_sign_oca_content_access(self, signer_id, access_token):
signer_sudo.request_id, "data"
).get_response(mimetype="application/pdf")

@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 @@ -92,11 +107,13 @@ 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")
return signer_sudo.action_sign(items, access_token=access_token)
return signer_sudo.action_sign(
items, encrypted_data=encrypted_data, access_token=access_token
)
1 change: 1 addition & 0 deletions sign_oca/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,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 @@ -345,13 +345,21 @@ 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()
model = fields.Char(compute="_compute_model", store=True)
res_id = fields.Integer(compute="_compute_res_id", store=True)
is_allow_signature = fields.Boolean(compute="_compute_is_allow_signature")
Expand Down Expand Up @@ -388,6 +396,10 @@ def _compute_is_allow_signature(self):
not item.signed_on and item.partner_id == user.partner_id
)

@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()
for record in self:
Expand All @@ -412,6 +424,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.partner_id.id,
"name": self.partner_id.name,
Expand All @@ -430,7 +443,7 @@ def sign(self):
"url": self.access_url,
}

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 @@ -439,6 +452,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 @@ -17,3 +17,5 @@ edit_sign_generate_signer,edit_sign_generate_signer,model_sign_oca_template_gene
edit_sign_generate_multi,edit_sign_generate_multi,model_sign_oca_template_generate_multi,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_request_log_admin,access_sign_request_log_admin,model_sign_oca_request_log,sign_oca_group_admin,1,1,1,1
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
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,15 +41,26 @@ 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},
})
.then((action) => {
// As we are on frontend env, it is not possible to use do_action(), so we
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 8b4c57b

Please sign in to comment.