Skip to content

Commit 13c2df4

Browse files
committed
created Base SPP VCI Issuer module
1 parent 680f30d commit 13c2df4

File tree

12 files changed

+320
-0
lines changed

12 files changed

+320
-0
lines changed

spp_openid_vci/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import models
2+
from . import wizard

spp_openid_vci/__manifest__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
4+
{
5+
"name": "SPP Registry OpenID VCI: Base",
6+
"category": "OpenSPP",
7+
"version": "17.0.1.0.0",
8+
"sequence": 1,
9+
"author": "OpenSPP.org",
10+
"website": "https://github.com/OpenSPP/openspp-modules",
11+
"license": "LGPL-3",
12+
"development_status": "Beta",
13+
"maintainers": ["jeremi", "gonzalesedwin1123"],
14+
"depends": [
15+
"spp_encryption",
16+
"g2p_encryption_rest_api",
17+
"g2p_registry_base",
18+
"g2p_openid_vci",
19+
"g2p_openid_vci_rest_api",
20+
],
21+
"external_dependencies": {"python": ["qrcode"]},
22+
"data": [
23+
"security/ir.model.access.csv",
24+
"wizard/vci_issuer_selection_view.xml",
25+
"data/paperformat.xml",
26+
"views/id_card.xml",
27+
],
28+
"assets": {},
29+
"demo": [],
30+
"images": [],
31+
"application": False,
32+
"installable": True,
33+
"auto_install": False,
34+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<odoo>
2+
<record id="paperformat_a4" model="report.paperformat">
3+
<field name="name">A4</field>
4+
<field name="default" eval="False" />
5+
<field name="format">A4</field>
6+
<field name="page_height">0</field>
7+
<field name="page_width">0</field>
8+
<field name="orientation">Portrait</field>
9+
<field name="margin_top">40</field>
10+
<field name="margin_bottom">40</field>
11+
<field name="margin_left">20</field>
12+
<field name="margin_right">20</field>
13+
<field name="header_line" eval="False" />
14+
<field name="header_spacing">35</field>
15+
<field name="dpi">90</field>
16+
</record>
17+
</odoo>

spp_openid_vci/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import res_partner
2+
from . import vci_issuer
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import base64
2+
import calendar
3+
import json
4+
from datetime import datetime
5+
from io import BytesIO
6+
7+
import qrcode
8+
import requests
9+
from qrcode.image.pil import PilImage
10+
11+
from odoo import _, fields, models
12+
from odoo.exceptions import UserError
13+
14+
15+
class SPPRegistry(models.Model):
16+
_inherit = "res.partner"
17+
18+
vc_qr_code = fields.Binary(string="VC QR Code", attachment=True)
19+
20+
def _validate_vci_issuer(self, vci_issuer):
21+
if not vci_issuer:
22+
raise UserError("No issuer found.")
23+
24+
if not vci_issuer.auth_sub_id_type_id:
25+
raise UserError("No auth sub id type found in the issuer.")
26+
27+
if not vci_issuer.encryption_provider_id:
28+
raise UserError("No encryption provider found in the issuer.")
29+
30+
def _issue_vc(self, vci_issuer):
31+
self.ensure_one()
32+
33+
encryption_provider_id = vci_issuer.encryption_provider_id
34+
35+
reg_id = self.env["g2p.reg.id"].search(
36+
[("partner_id", "=", self.id), ("id_type", "=", vci_issuer.auth_sub_id_type_id.id)]
37+
)
38+
if not reg_id:
39+
raise UserError(f"No Registrant found with this ID Type: {vci_issuer.auth_sub_id_type_id.name}.")
40+
41+
web_base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url").rstrip("/")
42+
43+
url = f"{web_base_url}/api/v1/vci/.well-known/openid-credential-issuer/{vci_issuer.name}"
44+
45+
credential_issuer_response = requests.get(url)
46+
47+
issuer_data = credential_issuer_response.json()
48+
49+
credential_issuer = f"{issuer_data['credential_issuer']}/api/v1/security"
50+
credentials_supported = issuer_data.get("credentials_supported", None)
51+
credential_request = credentials_supported[0]
52+
53+
today = datetime.today()
54+
55+
encryption_provider_id = vci_issuer.encryption_provider_id
56+
57+
dict_data = {
58+
"sub": reg_id.value,
59+
"name": "OpenSPP",
60+
"iat": calendar.timegm(today.timetuple()),
61+
"scope": vci_issuer.scope,
62+
"iss": credential_issuer,
63+
}
64+
65+
signed_data = encryption_provider_id.jwt_sign(data=dict_data)
66+
67+
return self.env["g2p.openid.vci.issuers"].issue_vc(credential_request, signed_data)
68+
69+
def _create_qr_code(self, data):
70+
qr = qrcode.QRCode(
71+
error_correction=qrcode.constants.ERROR_CORRECT_L,
72+
image_factory=PilImage,
73+
box_size=10,
74+
border=4,
75+
)
76+
77+
qr.add_data(data)
78+
qr.make(fit=True)
79+
80+
img = qr.make_image()
81+
82+
temp = BytesIO()
83+
img.save(temp, format="PNG")
84+
qr_img = base64.b64encode(temp.getvalue())
85+
temp.close()
86+
87+
return qr_img
88+
89+
def registry_issue_card(self):
90+
self.ensure_one()
91+
92+
form_id = self.env.ref("spp_openid_vci.issue_card_wizard").id
93+
action = {
94+
"name": _("Issue Card"),
95+
"type": "ir.actions.act_window",
96+
"view_mode": "form",
97+
"view_id": form_id,
98+
"view_type": "form",
99+
"res_model": "spp.issue.card.wizard",
100+
"target": "new",
101+
"context": {
102+
"default_partner_id": self.id,
103+
},
104+
}
105+
return action
106+
107+
def _issue_vc_qr(self, vci_issuer):
108+
self.ensure_one()
109+
110+
self._validate_vci_issuer(vci_issuer)
111+
112+
result = self._issue_vc(vci_issuer)
113+
114+
qr_img = self._create_qr_code(json.dumps(result))
115+
116+
self.vc_qr_code = qr_img
117+
118+
admission_form = self.env.ref("spp_openid_vci.action_generate_id_card").report_action(self)
119+
return admission_form
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from odoo import api, models
2+
3+
4+
class CustomOpenIDVCIssuer(models.Model):
5+
_inherit = "g2p.openid.vci.issuers"
6+
7+
@api.constrains("auth_allowed_issuers", "issuer_type")
8+
def onchange_auth_allowed_issuers(self):
9+
for rec in self:
10+
if not rec.auth_allowed_issuers:
11+
args = [rec, f"set_default_auth_allowed_issuers_{rec.issuer_type}"]
12+
if hasattr(*args):
13+
getattr(*args)()

spp_openid_vci/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["whool"]
3+
build-backend = "whool.buildapi"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2+
ir_attachment_crypto_admin_access,IR Attachment Crypto Admin Access,base.model_ir_attachment,g2p_encryption.crypto_admin,1,1,1,1
3+
ir_attachment_admin_access,IR Attachment Admin Access,base.model_ir_attachment,g2p_registry_base.group_g2p_admin,1,1,1,1
4+
ir_attachment_registrar_access,IR Attachment Registrar Access,base.model_ir_attachment,g2p_registry_base.group_g2p_registrar,1,1,1,1
5+
6+
spp_issue_card_wizard_crypto_admin,Issue Card Wizard Crypto Admin Access,spp_openid_vci.model_spp_issue_card_wizard,g2p_encryption.crypto_admin,1,1,1,1
7+
spp_issue_card_wizard_admin,Issue Card Wizard Admin Access,spp_openid_vci.model_spp_issue_card_wizard,g2p_registry_base.group_g2p_admin,1,1,1,1
8+
spp_issue_card_wizard_registrar,Issue Card Wizard Registrar Access,spp_openid_vci.model_spp_issue_card_wizard,g2p_registry_base.group_g2p_registrar,1,1,1,0

spp_openid_vci/views/id_card.xml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<odoo>
3+
4+
<record id="action_generate_id_card" model="ir.actions.report">
5+
<field name="name">ID Card</field>
6+
<field name="model">res.partner</field>
7+
<field name="report_type">qweb-pdf</field>
8+
<field name="report_name">spp_openid_vci.id_vc_card</field>
9+
<field name="report_file">spp_openid_vci.id_vc_card</field>
10+
<field name="print_report_name">'ID Card - %s' % (object.name).replace('/', '').strip()</field>
11+
<field name="paperformat_id" ref="spp_openid_vci.paperformat_a4" />
12+
<field name="binding_model_id" ref="model_res_partner" />
13+
<field name="binding_type">report</field>
14+
</record>
15+
16+
<template id="id_vc_card">
17+
<t t-call="web.basic_layout">
18+
<style>
19+
.id-card-table {
20+
width: 100%;
21+
border-collapse: collapse;
22+
margin-top: 20px;
23+
}
24+
.id-card-table{
25+
border: 2px solid black;
26+
}
27+
.id-card-table td {
28+
padding: 10px;
29+
vertical-align: middle;
30+
}
31+
.id-card-fields {
32+
width: 65%;
33+
}
34+
.id-card-image {
35+
width: 35%;
36+
text-align: left;
37+
}
38+
.id-card-image img {
39+
max-width: 100%;
40+
height: auto;
41+
}
42+
.tr-title td {
43+
text-align: center;
44+
margin-bottom: 16px;
45+
}
46+
</style>
47+
<t t-foreach="docs" t-as="doc">
48+
<div class="page">
49+
<table class="id-card-table">
50+
<tr class="tr-title">
51+
<td colspan="2"><h2>ID Card</h2></td>
52+
</tr>
53+
<tr>
54+
<!-- Fields on the left -->
55+
<td class="id-card-fields">
56+
<strong>Name:</strong> <small t-field="doc.name" /><br />
57+
<t t-if="not doc.is_group">
58+
<strong>Birthdate:</strong> <small t-field="doc.birthdate" /><br />
59+
<strong>Gender:</strong> <small t-field="doc.gender" /><br />
60+
</t>
61+
<strong>Address:</strong> <small t-field="doc.address" /><br />
62+
</td>
63+
<!-- QR Code on the right -->
64+
<td class="id-card-image">
65+
<img
66+
t-att-src="'data:image/png;base64,%s' % doc.vc_qr_code.decode()"
67+
alt="QR Code"
68+
class="fit-image"
69+
/>
70+
</td>
71+
</tr>
72+
</table>
73+
</div>
74+
</t>
75+
</t>
76+
</template>
77+
</odoo>

spp_openid_vci/wizard/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import vci_issuer_selection

0 commit comments

Comments
 (0)