diff --git a/g2p_entitlement_voucher/models/entitlement_manager.py b/g2p_entitlement_voucher/models/entitlement_manager.py index 880d7006..48815101 100644 --- a/g2p_entitlement_voucher/models/entitlement_manager.py +++ b/g2p_entitlement_voucher/models/entitlement_manager.py @@ -8,14 +8,6 @@ _logger = logging.getLogger(__name__) -class G2PCryptoKeySet(models.Model): - _inherit = "g2p.crypto.key.set" - - voucher_manager_id = fields.Many2one( - "g2p.program.entitlement.manager.voucher", ondelete="cascade" - ) - - class EntitlementManager(models.Model): _inherit = "g2p.program.entitlement.manager" @@ -33,21 +25,12 @@ class G2PVoucherEntitlementManager(models.Model): _inherit = "g2p.program.entitlement.manager.default" _description = "Voucher Entitlement Manager" - # This is a one2one relation - crypto_key_set = fields.One2many("g2p.crypto.key.set", "voucher_manager_id") - auto_generate_voucher_on_approval = fields.Boolean(default=True) voucher_file_config = fields.Many2one("g2p.payment.file.config") voucher_document_store = fields.Many2one("storage.backend", required=True) - @api.model - def create(self, values): - if not values.get("crypto_key_set", None): - values["crypto_key_set"] = [(0, 0, {})] - return super(G2PVoucherEntitlementManager, self).create(values) - def open_voucher_config_form(self): if self.voucher_file_config: return { @@ -137,7 +120,7 @@ def _generate_vouchers(self, entitlements): qrcode_config.render_datas_and_store( "g2p.entitlement", entitlements.ids, - self.crypto_key_set[0], + self.get_encryption_provider(), res_id_field_in_qrcode_model="entitlement_id", ) diff --git a/g2p_entitlement_voucher/security/ir.model.access.csv b/g2p_entitlement_voucher/security/ir.model.access.csv index 6fcf9aa1..1a763470 100644 --- a/g2p_entitlement_voucher/security/ir.model.access.csv +++ b/g2p_entitlement_voucher/security/ir.model.access.csv @@ -1,9 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -g2p_payment_files.access_crypto_key_set_admin,access_crypto_key_set_admin,g2p_payment_files.model_g2p_crypto_key_set,g2p_registry_base.group_g2p_admin,1,1,1,1 -g2p_payment_files.access_crypto_key_set_manager,access_crypto_key_set_manager,g2p_payment_files.model_g2p_crypto_key_set,g2p_programs.g2p_program_manager,1,1,1,0 -g2p_payment_files.access_crypto_key_set_validator,access_crypto_key_set_validator,g2p_payment_files.model_g2p_crypto_key_set,g2p_programs.g2p_program_validator,1,0,0,0 - g2p_entitlement_voucher.access_entitlement_manager_voucher_admin,access_entitlement_manager_voucher_admin,g2p_entitlement_voucher.model_g2p_program_entitlement_manager_voucher,g2p_registry_base.group_g2p_admin,1,1,1,1 g2p_entitlement_voucher.access_entitlement_manager_voucher_manager,access_entitlement_manager_voucher_manager,g2p_entitlement_voucher.model_g2p_program_entitlement_manager_voucher,g2p_programs.g2p_program_manager,1,1,1,0 g2p_entitlement_voucher.access_entitlement_manager_voucher_validator,access_entitlement_manager_voucher_validator,g2p_entitlement_voucher.model_g2p_program_entitlement_manager_voucher,g2p_programs.g2p_program_validator,1,0,0,0 diff --git a/g2p_import_dci_api/__manifest__.py b/g2p_import_dci_api/__manifest__.py index 003abaf8..3d6c2870 100644 --- a/g2p_import_dci_api/__manifest__.py +++ b/g2p_import_dci_api/__manifest__.py @@ -5,7 +5,6 @@ "version": "15.0.1.1.0", "author": "OpenG2P", "development_status": "Alpha", - "external_dependencies": {"python": ["PyLD", "pyjwt>=2.4.0"]}, "website": "https://openg2p.org", "license": "Other OSI approved licence", "depends": [ diff --git a/g2p_payment_cash/models/payment_manager.py b/g2p_payment_cash/models/payment_manager.py index 2e60c329..0d592996 100644 --- a/g2p_payment_cash/models/payment_manager.py +++ b/g2p_payment_cash/models/payment_manager.py @@ -22,14 +22,6 @@ def _selection_manager_ref_id(self): return selection -class G2PCryptoKeySet(models.Model): - _inherit = "g2p.crypto.key.set" - - cash_payment_manager_id = fields.Many2one( - "g2p.program.payment.manager.cash", ondelete="cascade" - ) - - class G2PPaymentManagerCash(models.Model): _name = "g2p.program.payment.manager.cash" _inherit = "g2p.program.payment.manager.file" @@ -42,9 +34,6 @@ class G2PPaymentManagerCash(models.Model): ondelete="cascade", ) - # This is a one2one relation - crypto_key_set = fields.One2many("g2p.crypto.key.set", "cash_payment_manager_id") - # This will just mark all the payments as done when then cash is given out def _send_payments(self, batches): if not batches: diff --git a/g2p_payment_files/__init__.py b/g2p_payment_files/__init__.py index 84518a19..2c0aa80c 100644 --- a/g2p_payment_files/__init__.py +++ b/g2p_payment_files/__init__.py @@ -1,4 +1,2 @@ # Part of OpenG2P. See LICENSE file for full copyright and licensing details. from . import models -from . import controllers -from . import services diff --git a/g2p_payment_files/__manifest__.py b/g2p_payment_files/__manifest__.py index e91734be..66c8e878 100644 --- a/g2p_payment_files/__manifest__.py +++ b/g2p_payment_files/__manifest__.py @@ -11,15 +11,13 @@ "depends": [ "g2p_programs", "g2p_program_documents", - "g2p_programs_rest_api", + "g2p_encryption", "mail", ], "external_dependencies": { "python": [ "base45", - "cryptography", "cose", - "python-jose", "python-barcode", "pdfkit", "qrcode", diff --git a/g2p_payment_files/controllers/__init__.py b/g2p_payment_files/controllers/__init__.py deleted file mode 100644 index 8ac3dc08..00000000 --- a/g2p_payment_files/controllers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import payments diff --git a/g2p_payment_files/controllers/payments.py b/g2p_payment_files/controllers/payments.py deleted file mode 100644 index f52f3e63..00000000 --- a/g2p_payment_files/controllers/payments.py +++ /dev/null @@ -1,7 +0,0 @@ -from odoo.addons.base_rest.controllers import main - - -class RegistryApiController(main.RestController): - _root_path = "/api/v1/payments/" - _collection_name = "base.rest.payment.services" - _default_auth = "user" diff --git a/g2p_payment_files/models/__init__.py b/g2p_payment_files/models/__init__.py index a6589ba4..47b2ce65 100644 --- a/g2p_payment_files/models/__init__.py +++ b/g2p_payment_files/models/__init__.py @@ -1,4 +1,3 @@ -from . import payment_key_set from . import payment_file_config from . import payment_file_qrcode_config from . import payment_manager diff --git a/g2p_payment_files/models/payment_file_qrcode_config.py b/g2p_payment_files/models/payment_file_qrcode_config.py index fa346a71..d9d70de9 100644 --- a/g2p_payment_files/models/payment_file_qrcode_config.py +++ b/g2p_payment_files/models/payment_file_qrcode_config.py @@ -5,7 +5,6 @@ import qrcode import qrcode.image.svg from barcode import Code128 # pylint: disable=[W7936] -from jose import jwt # pylint: disable=[W7936] from odoo import _, api, fields, models from odoo.exceptions import ValidationError @@ -64,7 +63,7 @@ def render_datas_and_store( self, res_model, res_ids, - crypto_ket_set_id, + encryption_provider, res_id_field_in_qrcode_model=None, template_engine="inline_template", ): @@ -73,7 +72,7 @@ def render_datas_and_store( self.body_string, res_model, res_ids, - crypto_ket_set_id, + encryption_provider, template_engine=template_engine, ) create_vals = [] @@ -95,7 +94,7 @@ def _render_data( template_src, res_model, res_ids, - key_set, + encryption_provider, template_engine="inline_template", ): RenderMixin = self.env["mail.render.mixin"] @@ -112,13 +111,9 @@ def _render_data( # the following should throw exception if not json json.loads(datas[res_id]) elif data_type == "jwt": - kid = key_set.name - priv_key = key_set.priv_key.encode() for res_id in res_ids: payload = json.loads(datas[res_id]) - datas[res_id] = jwt.encode( - payload, priv_key, algorithm="RS256", headers={"kid": kid} - ) + datas[res_id] = encryption_provider.jwt_sign(payload) return datas diff --git a/g2p_payment_files/models/payment_key_set.py b/g2p_payment_files/models/payment_key_set.py deleted file mode 100644 index f6eae62d..00000000 --- a/g2p_payment_files/models/payment_key_set.py +++ /dev/null @@ -1,146 +0,0 @@ -import base64 -import json -import uuid -from datetime import datetime, timedelta -from urllib.parse import urlparse - -from cryptography import x509 -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.x509.oid import NameOID -from jose import constants, jwk # pylint: disable=[W7936] - -from odoo import api, fields, models - - -class G2PCryptoKeySet(models.Model): - _name = "g2p.crypto.key.set" - _description = "G2P Crypto Key Set" - _order = "id desc" - - name = fields.Char(string="kid", required=True) - - type = fields.Selection( - [ - ("rsa", "RSA"), - ], - default="rsa", - ) - - size = fields.Selection( - [ - ("2048", "2048"), - ("3072", "3072"), - ("4096", "4096"), - ], - default="2048", - ) - - priv_key = fields.Char(compute="_compute_priv_key", store=True) - pub_cert = fields.Char(compute="_compute_pub_cert", store=True) - - jwk = fields.Char(compute="_compute_jwk", store=True) - - use = fields.Selection( - [ - ("sig", "Signature"), - ("enc", "Encryption"), - ], - default="sig", - ) - status = fields.Selection( - [ - ("active", "Active"), - ], - default="active", - ) - - file_payment_manager_id = fields.Many2one( - "g2p.program.payment.manager.file", ondelete="cascade" - ) - - _sql_constraints = [ - ( - "name_unique", - "unique (name)", - "Name/KID of the key set should be unique", - ), - ] - - @api.model - def create(self, values): - if not values.get("name", None): - values["name"] = str(uuid.uuid4()) - return super(G2PCryptoKeySet, self).create(values) - - @api.depends("type", "size") - def _compute_priv_key(self): - for rec in self: - if rec.type == "rsa": - priv_key = rsa.generate_private_key(65537, int(rec.size)) - rec.priv_key = priv_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - # Hardcoding no encryption for now - encryption_algorithm=serialization.NoEncryption(), - ).decode() - else: - rec.priv_key = None - - @api.depends("priv_key") - def _compute_pub_cert(self): - web_base_url = self.env["ir.config_parameter"].get_param("web.base.url") - web_base_hostname = urlparse(web_base_url).netloc - for rec in self: - if rec.type == "rsa": - priv_key = serialization.load_pem_private_key( - data=rec.priv_key.encode(), - password=None, - ) - pub_key = priv_key.public_key() - sub_name = x509.Name( - [x509.NameAttribute(NameOID.COMMON_NAME, web_base_hostname)] - ) - alt_names = [x509.DNSName(web_base_hostname)] - san = x509.SubjectAlternativeName(alt_names) - - utcnow = datetime.utcnow() - # No signer as of now. - # TODO: Add a common signer cert for openg2p, - # which signs every newly created cert. - # (Probably a oca/vault based implementation) - cert = ( - x509.CertificateBuilder() - .subject_name(sub_name) - .issuer_name(sub_name) - .public_key(pub_key) - .serial_number(1000) - .not_valid_before(utcnow) - .not_valid_after(utcnow + timedelta(days=10 * 365)) - .add_extension(san, False) - .sign(priv_key, hashes.SHA256()) - ) - rec.pub_cert = cert.public_bytes(encoding=serialization.Encoding.PEM) - else: - rec.pub_cert = None - - @api.depends("pub_cert", "use") - def _compute_jwk(self): - for key_set in self: - certificate = x509.load_pem_x509_certificate(key_set.pub_cert.encode()) - cert_x5c = base64.b64encode( - certificate.public_bytes(serialization.Encoding.DER) - ).decode() - pub_key = certificate.public_key() - # TODO: For now hardcoding alogrithm - pub_key_jwk = jwk.RSAKey( - algorithm=constants.ALGORITHMS.RS256, - key=pub_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ).decode(), - ).to_dict() - pub_key_jwk["kid"] = key_set.name - pub_key_jwk["use"] = key_set.use - pub_key_jwk["x5c"] = [cert_x5c] - key_set.jwk = json.dumps(pub_key_jwk) diff --git a/g2p_payment_files/models/payment_manager.py b/g2p_payment_files/models/payment_manager.py index 560a6051..7973977d 100644 --- a/g2p_payment_files/models/payment_manager.py +++ b/g2p_payment_files/models/payment_manager.py @@ -30,14 +30,7 @@ class G2PFilesPaymentManager(models.Model): payment_file_config_ids = fields.Many2many("g2p.payment.file.config") - # This is a one2one relation - crypto_key_set = fields.One2many("g2p.crypto.key.set", "file_payment_manager_id") - - @api.model - def create(self, values): - if not values.get("crypto_key_set", None): - values["crypto_key_set"] = [(0, 0, {})] - return super(G2PFilesPaymentManager, self).create(values) + encryption_provider_id = fields.Many2one("g2p.encryption.provider") batch_tag_ids = fields.Many2many( "g2p.payment.batch.tag", @@ -83,7 +76,7 @@ def _prepare_payments(self, cycle, entitlements): qrcode_config.render_datas_and_store( render_res_model, render_res_ids, - self.crypto_key_set[0], + self.get_encryption_provider(), res_id_field_in_qrcode_model, ) @@ -106,7 +99,7 @@ def _prepare_payments(self, cycle, entitlements): qrcode_config.render_datas_and_store( "g2p.payment", payments.ids, - self.crypto_key_set[0], + self.get_encryption_provider(), res_id_field_in_qrcode_model="payment_id", ) @@ -122,6 +115,13 @@ def _prepare_payments(self, cycle, entitlements): def _send_payments(self, batches): raise NotImplementedError() + def get_encryption_provider(self): + self.ensure_one() + prov = self.encryption_provider_id + if not prov: + prov = self.env.ref("g2p_encryption.encryption_provider_default") + return prov + class G2PPaymentBatchTag(models.Model): _inherit = "g2p.payment.batch.tag" diff --git a/g2p_payment_files/security/ir.model.access.csv b/g2p_payment_files/security/ir.model.access.csv index 832b1f40..d7b70131 100644 --- a/g2p_payment_files/security/ir.model.access.csv +++ b/g2p_payment_files/security/ir.model.access.csv @@ -1,9 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -g2p_payment_files.access_crypto_key_set_admin,access_crypto_key_set_admin,g2p_payment_files.model_g2p_crypto_key_set,g2p_registry_base.group_g2p_admin,1,1,1,1 -g2p_payment_files.access_crypto_key_set_manager,access_crypto_key_set_manager,g2p_payment_files.model_g2p_crypto_key_set,g2p_programs.g2p_program_manager,1,1,1,0 -g2p_payment_files.access_crypto_key_set_user,access_crypto_key_set_user,g2p_payment_files.model_g2p_crypto_key_set,base.group_user,1,0,0,0 - g2p_payment_files.access_file_config_admin,access_file_config_admin,g2p_payment_files.model_g2p_payment_file_config,g2p_registry_base.group_g2p_admin,1,1,1,1 g2p_payment_files.access_file_config_manager,access_file_config_manager,g2p_payment_files.model_g2p_payment_file_config,g2p_programs.g2p_program_manager,1,1,1,0 g2p_payment_files.access_file_config_user,access_file_config_user,g2p_payment_files.model_g2p_payment_file_config,base.group_user,1,0,0,0 diff --git a/g2p_payment_files/services/__init__.py b/g2p_payment_files/services/__init__.py deleted file mode 100644 index 403ae06b..00000000 --- a/g2p_payment_files/services/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import well_known diff --git a/g2p_payment_files/services/well_known.py b/g2p_payment_files/services/well_known.py deleted file mode 100644 index 69ee94e7..00000000 --- a/g2p_payment_files/services/well_known.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -import logging - -from odoo.addons.base_rest import restapi -from odoo.addons.component.core import Component - -_logger = logging.getLogger(__name__) - - -class WellknownRestComponent(Component): - _name = "payments_well_known.rest.service" - _inherit = ["base.rest.service"] - _usage = ".well-known" - _collection = "base.rest.payment.services" - _description = """ - Payment Well-Known API Services - """ - - @restapi.method( - [ - ( - [ - "/jwks.json", - ], - "GET", - ) - ], - auth="public", - ) - def get_jwks(self): - CryptoKeySet = self.env["g2p.crypto.key.set"].sudo() - key_sets = CryptoKeySet.search([("status", "=", "active")]) - jwks = [json.loads(key_set.jwk) for key_set in key_sets] - return {"keys": jwks} diff --git a/g2p_payment_files/views/payment_manager_view.xml b/g2p_payment_files/views/payment_manager_view.xml index e0cfa293..6ddded4c 100644 --- a/g2p_payment_files/views/payment_manager_view.xml +++ b/g2p_payment_files/views/payment_manager_view.xml @@ -24,6 +24,7 @@ Part of OpenG2P. See LICENSE file for full copyright and licensing details. + =2.4.0 -PyLD python-barcode python-dateutil -python-jose qrcode wkhtmltopdf diff --git a/test-requirements.txt b/test-requirements.txt index b50f1ca8..81a2663d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,3 +6,4 @@ git+https://github.com/OpenG2P/openg2p-registry@15.0-develop#subdirectory=setup/ git+https://github.com/OpenG2P/openg2p-registry@15.0-develop#subdirectory=setup/g2p_registry_rest_api git+https://github.com/OpenG2P/openg2p-documents@15.0-develop#subdirectory=setup/g2p_documents git+https://github.com/OpenG2P/odoo-json-field@15.0-develop#subdirectory=setup/g2p_json_field +git+https://github.com/OpenG2P/openg2p-security@15.0-develop#subdirectory=setup/g2p_encryption