From fdbe30c52c01a95739fbb85cea88a43c84321724 Mon Sep 17 00:00:00 2001 From: Kev-Roche Date: Mon, 27 May 2024 16:12:22 +0200 Subject: [PATCH] add desktop certificat --- desktop_certificate/README.rst | 70 +++ desktop_certificate/__init__.py | 1 + desktop_certificate/__manifest__.py | 28 ++ desktop_certificate/data/data.xml | 56 +++ desktop_certificate/models/__init__.py | 3 + desktop_certificate/models/desktop.py | 190 ++++++++ .../models/desktop_certificate.py | 91 ++++ desktop_certificate/readme/CONTRIBUTORS.rst | 1 + desktop_certificate/readme/DESCRIPTION.rst | 3 + .../security/ir.model.access.csv | 5 + desktop_certificate/security/res_groups.xml | 14 + desktop_certificate/security/rule.xml | 13 + .../static/description/index.html | 419 ++++++++++++++++++ desktop_certificate/tests/__init__.py | 1 + desktop_certificate/tests/test_certificats.py | 65 +++ desktop_certificate/views/desktop.xml | 125 ++++++ .../views/desktop_certificate.xml | 46 ++ .../odoo/addons/desktop_certificate | 1 + setup/desktop_certificate/setup.py | 6 + 19 files changed, 1138 insertions(+) create mode 100644 desktop_certificate/README.rst create mode 100644 desktop_certificate/__init__.py create mode 100644 desktop_certificate/__manifest__.py create mode 100644 desktop_certificate/data/data.xml create mode 100644 desktop_certificate/models/__init__.py create mode 100644 desktop_certificate/models/desktop.py create mode 100644 desktop_certificate/models/desktop_certificate.py create mode 100644 desktop_certificate/readme/CONTRIBUTORS.rst create mode 100644 desktop_certificate/readme/DESCRIPTION.rst create mode 100644 desktop_certificate/security/ir.model.access.csv create mode 100644 desktop_certificate/security/res_groups.xml create mode 100644 desktop_certificate/security/rule.xml create mode 100644 desktop_certificate/static/description/index.html create mode 100644 desktop_certificate/tests/__init__.py create mode 100644 desktop_certificate/tests/test_certificats.py create mode 100644 desktop_certificate/views/desktop.xml create mode 100644 desktop_certificate/views/desktop_certificate.xml create mode 120000 setup/desktop_certificate/odoo/addons/desktop_certificate create mode 100644 setup/desktop_certificate/setup.py diff --git a/desktop_certificate/README.rst b/desktop_certificate/README.rst new file mode 100644 index 000000000..0991cd32b --- /dev/null +++ b/desktop_certificate/README.rst @@ -0,0 +1,70 @@ +=================== +Desktop Certificats +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2bffd83f3e44d4fff1cab3eca36cc7170c1008fed1bf1fea9222d041e920e331 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-akretion%2Fak--odoo--incubator-lightgray.png?logo=github + :target: https://github.com/akretion/ak-odoo-incubator/tree/14.0/desktop_certificate + :alt: akretion/ak-odoo-incubator + +|badge1| |badge2| |badge3| + +This module allows to manage certificates TLS / MPKI / SSL. +It manages the creation and renewal of certificates and the installation of the certificates on your server. +It allows you to manage your certificates in a simple and efficient way. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Kévin Roche + +Maintainers +~~~~~~~~~~~ + +.. |maintainer-Kev-Roche| image:: https://github.com/Kev-Roche.png?size=40px + :target: https://github.com/Kev-Roche + :alt: Kev-Roche + +Current maintainer: + +|maintainer-Kev-Roche| + +This module is part of the `akretion/ak-odoo-incubator `_ project on GitHub. + +You are welcome to contribute. diff --git a/desktop_certificate/__init__.py b/desktop_certificate/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/desktop_certificate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/desktop_certificate/__manifest__.py b/desktop_certificate/__manifest__.py new file mode 100644 index 000000000..8508514ba --- /dev/null +++ b/desktop_certificate/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Desktop Certificats", + "summary": "Generate and manage TSL certificates of users", + "version": "14.0.1.0.0", + "category": "tools", + "website": "https://github.com/akretion/ak-odoo-incubator", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "maintainers": ["Kev-Roche"], + "application": False, + "installable": True, + "depends": [ + "stock", + "mail", + ], + "data": [ + "data/data.xml", + "security/res_groups.xml", + "security/ir.model.access.csv", + "security/rule.xml", + "views/desktop.xml", + "views/desktop_certificate.xml", + ], +} diff --git a/desktop_certificate/data/data.xml b/desktop_certificate/data/data.xml new file mode 100644 index 000000000..baddb177f --- /dev/null +++ b/desktop_certificate/data/data.xml @@ -0,0 +1,56 @@ + + + + mpki_api_url + http://front.home.arpa:8096/certs + + + mpki_api_user + + + + mpki_api_password + + + + + Email d'alerte d'expiration de certificats informatique. + + + 3 + days + -1 + + model.cron_send_email_certificate_expiration() + + + Email d'alerte d'expiration de certificats informatique + + Certificats à renouveler sous 15 jours + + +
+

+ --- MAIL AUTOMATIQUE ---

+ Bonjour,

+ Voici la liste des certificats qui expireront sous 15 jours: +
+

    + % for line in object.env.context.get('certifs_ids'): +
  • ${line.name} : ${line.expiration_date}
  • + % endfor +
+ +
+ Cordialement +

+
+
+ ${(object.name or '').replace('/','-')} + +
+
diff --git a/desktop_certificate/models/__init__.py b/desktop_certificate/models/__init__.py new file mode 100644 index 000000000..206dab761 --- /dev/null +++ b/desktop_certificate/models/__init__.py @@ -0,0 +1,3 @@ +from . import desktop_certificate + +from . import desktop diff --git a/desktop_certificate/models/desktop.py b/desktop_certificate/models/desktop.py new file mode 100644 index 000000000..51e74f3af --- /dev/null +++ b/desktop_certificate/models/desktop.py @@ -0,0 +1,190 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Kévin Roche + +import json +from datetime import datetime + +import requests + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class Desktop(models.Model): + _name = "desktop" + _inherit = ["mail.thread"] + _description = "Desktop" + + name = fields.Char(string="Nom") + location_id = fields.Many2one( + comodel_name="stock.warehouse", string="Lieu", required=True + ) + company_id = fields.Many2one( + comodel_name="res.company", + string="Société", + default=lambda self: self.env.company, + ) + certificate_partner_id = fields.Many2one( + comodel_name="res.partner", + string="Certificats envoyés à", + domain=[("user_ids", "!=", False)], + default=lambda self: self.env.user.partner_id, + ) + certificate_email = fields.Char( + string="Certificat envoyé à", related="certificate_partner_id.email" + ) + certificate_phone = fields.Char( + string="Mot de passe du certificat envoyé à", + related="certificate_partner_id.mobile", + ) + + certificate_ids = fields.One2many( + comodel_name="desktop.certificate", + inverse_name="desktop_id", + string="Certificats", + ) + certificate_state = fields.Selection( + selection=[ + ("1_short", "< 15 jours"), + ("2_medium", "< 3 mois"), + ("3_long", "> 3 mois"), + ("4_revoked", "Révoqué"), + ("5_obsolete", "Obsolète"), + ("none", "Aucun certificat"), + ], + string="Etat Certificats", + compute="_compute_valid_certificate_state", + store=True, + ) + + certificate_min_exp_date = fields.Date( + string="Date d'expiration", + compute="_compute_certificate_min_exp_date", + ) + + valid_certificate_count = fields.Integer( + compute="_compute_valid_certificate_count", string="Certificats Valides" + ) + + def _compute_certificate_min_exp_date(self): + for rec in self: + rec.certificate_min_exp_date = None + certifs = rec.certificate_ids + if certifs: + current_datetime = datetime.now() + valid_datetime_list = [ + certif.expiration_date + for certif in certifs + if certif.expiration_date >= current_datetime + and not certif.revoked + and certif.active + ] + if valid_datetime_list: + rec.certificate_min_exp_date = max(valid_datetime_list).date() + + def _compute_valid_certificate_state(self): + for rec in self: + certif_states = rec.certificate_ids.mapped("state") + if certif_states: + if "short" in certif_states: + rec.certificate_state = "1_short" + elif "medium" in certif_states: + rec.certificate_state = "2_medium" + elif "long" in certif_states: + rec.certificate_state = "3_long" + elif "revoked" in certif_states: + rec.certificate_state = "4_revoked" + else: + rec.certificate_state = "5_obsolete" + else: + rec.certificate_state = "none" + + def cron_send_email_certificate_expiration(self): + certifs = self.env["maintenance.certificate"].search([]) + certifs._compute_valid_certificate_state() + short_certifs = certifs.filtered(lambda x: x.state == "short") + if short_certifs: + email_template = self.env.ref("infra.mail_certificate_expiration") + self.with_context( + certifs_ids=short_certifs.equipment_id + ).message_post_with_template(email_template.id) + + @api.depends("certificate_ids") + def _compute_valid_certificate_count(self): + for record in self: + record.valid_certificate_count = len( + record.certificate_ids.filtered( + lambda c: not c.revoked and c.expiration_date > datetime.now() + ) + ) + + def _get_mpki_api(self): + ir_config_model = self.env["ir.config_parameter"].sudo() + mpki_api = {} + mpki_api["url"] = ir_config_model.get_param("mpki_api_url") + mpki_api["user"] = ir_config_model.get_param("mpki_api_user") + mpki_api["password"] = ir_config_model.get_param("mpki_api_password") + return mpki_api + + def generate_certificate(self): + mpki_api = self._get_mpki_api() + for record in self: + partner = record.certificate_partner_id + if not partner.email: + raise UserError(_("L'email du destinataire est vide")) + if not partner.mobile: + raise UserError(_("Le mobile du destinataire est vide")) + location = record.location_id.partner_id + params = { + "certificate": { + "name": record.name, + }, + "partner": { + "name": partner.display_name, + "phone": partner.mobile, + "email": partner.email, + }, + "location": { + "name": location.name, + "company": location.company_id.name, + "city": location.city, + "zipcode": location.zip, + "country": location.country_id.name, + }, + } + res = requests.post( + mpki_api["url"], + auth=(mpki_api["user"], mpki_api["password"]), + json=params, + ) + if res.status_code == requests.codes.ok: + res_dict = res.json() + self.env["desktop.certificate"].create( + { + "revoked": not res_dict["valid"], + "name": res_dict["name"], + "expiration_date": datetime.fromisoformat( + res_dict["valid_until"] + ), + "desktop_id": record.id, + "sent_to_email": record.certificate_email, + "sent_to_phone": record.certificate_phone, + } + ) + else: + try: + raise UserError(json.dumps(res.json(), indent=4)) + except ValueError: + raise UserError(res.text) + return None + + @api.onchange("location_id") + def _onchange_location(self): + if self.location_id: + self.company_id = self.location_id.company_id.id or False + + def unlink(self): + desktops = self.mapped("desktop_id") + super().unlink() + desktops.unlink() + return True diff --git a/desktop_certificate/models/desktop_certificate.py b/desktop_certificate/models/desktop_certificate.py new file mode 100644 index 000000000..8cda56dea --- /dev/null +++ b/desktop_certificate/models/desktop_certificate.py @@ -0,0 +1,91 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Kévin Roche +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json +from datetime import datetime + +import requests +from dateutil.relativedelta import relativedelta + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class DesktopCertificate(models.Model): + _name = "desktop.certificate" + _description = "Desktop Certificate" + + name = fields.Char(string="Nom") + desktop_id = fields.Many2one( + comodel_name="desktop", + string="Poste", + ) + active = fields.Boolean(default=True, string="Actif") + revoked = fields.Boolean(string="Révoqué", default=False) + expiration_date = fields.Datetime(string="Date d'expiration") + sent_to_email = fields.Char(string="Destinataire mail") + sent_to_phone = fields.Char( + string="Destinataire SMS", + ) + company_id = fields.Many2one( + comodel_name="res.company", + related="desktop_id.company_id", + ) + + state = fields.Selection( + selection=[ + ("obsolete", "Obsolète"), + ("revoked", "Révoqué"), + ("long", "> 3 mois"), + ("medium", "< 3 mois"), + ("short", "< 15 jours"), + ], + string="Etat du Certificat", + compute="_compute_state", + store=False, + ) + + def _compute_state(self): + for rec in self: + if rec.revoked or not rec.active: + rec.state = "revoked" + elif rec.expiration_date < datetime.now(): + rec.state = "obsolete" + elif rec.expiration_date < datetime.now() + relativedelta(weeks=2): + rec.state = "short" + elif rec.expiration_date < datetime.now() + relativedelta(months=3): + rec.state = "medium" + else: + rec.state = "long" + + def action_archive(self): + mpki_api = self[0].equipment_id._get_mpki_api() + usable = self.filtered( + lambda c: c.expiration_date > datetime.now() and not c.revoked + ) + for record in usable: + res = requests.delete( + mpki_api["url"], + auth=(mpki_api["user"], mpki_api["password"]), + params={"serial": record.serial}, + ) + # Archive if certificate is revoked or not found (already revoked) + if res.status_code == requests.codes.ok or res.status_code == 404: + record.revoked = True + super(DesktopCertificate, record).action_archive() + else: + try: + raise UserError(json.dumps(res.json(), indent=4)) + except ValueError: + raise UserError(res.text) + # standard archive process for unusable certificates + return super(DesktopCertificate, self - usable).action_archive() + + def action_unarchive(self): + raise UserError( + _( + "Vous ne pouvez pas désarchiver un certificat, " + "veuillez en générer un nouveau." + ) + ) diff --git a/desktop_certificate/readme/CONTRIBUTORS.rst b/desktop_certificate/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..5ef178f72 --- /dev/null +++ b/desktop_certificate/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Kévin Roche \ No newline at end of file diff --git a/desktop_certificate/readme/DESCRIPTION.rst b/desktop_certificate/readme/DESCRIPTION.rst new file mode 100644 index 000000000..2c7364600 --- /dev/null +++ b/desktop_certificate/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to manage certificates TLS / MPKI / SSL. +It manages the creation and renewal of certificates and the installation of the certificates on your server. +It allows you to manage your certificates in a simple and efficient way. diff --git a/desktop_certificate/security/ir.model.access.csv b/desktop_certificate/security/ir.model.access.csv new file mode 100644 index 000000000..0e45b0a78 --- /dev/null +++ b/desktop_certificate/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_desktop_manager,desktop.manager,model_desktop,desktop_certificate.group_certificat_manager,1,1,1,0 +access_desktop,desktop.user,model_desktop,base.group_user,1,0,0,0 +access_desktop_certificate_manager,desktop.certificate.manager,model_desktop_certificate,desktop_certificate.group_certificat_manager,1,1,1,0 +access_desktop_certificate,desktop.certificate.user,model_desktop_certificate,base.group_user,1,0,0,0 diff --git a/desktop_certificate/security/res_groups.xml b/desktop_certificate/security/res_groups.xml new file mode 100644 index 000000000..abcb69520 --- /dev/null +++ b/desktop_certificate/security/res_groups.xml @@ -0,0 +1,14 @@ + + + + + Gestionnaire des certificats utilisateurs + Permet de générer/révoquer les certificats + + + diff --git a/desktop_certificate/security/rule.xml b/desktop_certificate/security/rule.xml new file mode 100644 index 000000000..1fa574a24 --- /dev/null +++ b/desktop_certificate/security/rule.xml @@ -0,0 +1,13 @@ + + + + Certificat multicompany rule + + + + + + [('company_id', 'in', company_ids)] + + + diff --git a/desktop_certificate/static/description/index.html b/desktop_certificate/static/description/index.html new file mode 100644 index 000000000..577c8292e --- /dev/null +++ b/desktop_certificate/static/description/index.html @@ -0,0 +1,419 @@ + + + + + +Desktop Certificats + + + +
+

Desktop Certificats

+ + +

Beta License: AGPL-3 akretion/ak-odoo-incubator

+

This module allows to manage certificates TLS / MPKI / SSL. +It manages the creation and renewal of certificates and the installation of the certificates on your server. +It allows you to manage your certificates in a simple and efficient way.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

Current maintainer:

+

Kev-Roche

+

This module is part of the akretion/ak-odoo-incubator project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/desktop_certificate/tests/__init__.py b/desktop_certificate/tests/__init__.py new file mode 100644 index 000000000..84962768b --- /dev/null +++ b/desktop_certificate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_certificats diff --git a/desktop_certificate/tests/test_certificats.py b/desktop_certificate/tests/test_certificats.py new file mode 100644 index 000000000..f92124b04 --- /dev/null +++ b/desktop_certificate/tests/test_certificats.py @@ -0,0 +1,65 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Pierrick Brun +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase + +try: + from vcr_unittest import VCRMixin +except ImportError: + VCRMixin = None + + +class TestCertificateApi(TransactionCase, VCRMixin): + def setUp(self): + super().setUp() + warehouse = self.env.ref("stock.warehouse0") + contact = self.env["res.partner"].create( + { + "name": "Jhon Doe", + "email": "jhon.doe@akretion.com", + "mobile": "+33699999999", + } + ) + self.desktop = self.env["desktop"].create( + { + "location_id": warehouse.id, + "name": "TEST", + "certificate_partner_id": contact.id, + } + ) + self._set_mpki_api() + + def _set_mpki_api(self): + ir_config_model = self.env["ir.config_parameter"].sudo() + # This is the IP address of the docker host + ir_config_model.set_param("mpki_api_url", "http://172.17.0.1:8000/certs") + ir_config_model.set_param("mpki_api_user", "abilis") + ir_config_model.set_param("mpki_api_password", "TESTTESTTESTTEST") + + def _get_vcr_kwargs(self, **kwargs): + return { + "record_mode": "once", + "match_on": ["method", "path", "query"], + "filter_headers": ["Authorization"], + "decode_compressed_response": True, + } + + def test_generate_certificate(self): + self.assertEqual(len(self.desktop.certificate_ids), 0) + self.desktop.generate_certificate() + self.assertEqual(len(self.desktop.certificate_ids), 1) + certificate1 = self.desktop.certificate_ids + self.desktop.generate_certificate() + self.assertEqual(len(self.desktop.certificate_ids), 2) + for cert in self.desktop.certificate_ids: + self.assertEqual(cert.sent_to_email, "jhon.doe@akretion.com") + self.assertEqual(cert.sent_to_phone, "+33699999999") + self.assertEqual(cert.revoked, False) + certificate1.action_archive() + self.assertEqual(certificate1.revoked, True) + with self.assertRaises(UserError): + certificate1.action_unarchive() + self.assertEqual(len(self.desktop.certificate_ids), 1) diff --git a/desktop_certificate/views/desktop.xml b/desktop_certificate/views/desktop.xml new file mode 100644 index 000000000..ddcfe1a61 --- /dev/null +++ b/desktop_certificate/views/desktop.xml @@ -0,0 +1,125 @@ + + + + + Certificats + desktop.certificate + tree + [("desktop_id", "=", active_id)] + {"active_test": False} + + + + + + desktop + +
+
+
+ +
+ +
+ + + + + + + + + + +
+
+
+
+ + + + + desktop + + + + + + + + + + + + + + desktop + + + + + + + + + + + Générer le(s) certificat(s) + + + code + action = model.browse(env.context['active_ids']).generate_certificate() + + + + + Postes + desktop + tree,form + {"active_test": False} + + +
diff --git a/desktop_certificate/views/desktop_certificate.xml b/desktop_certificate/views/desktop_certificate.xml new file mode 100644 index 000000000..3af7edf3f --- /dev/null +++ b/desktop_certificate/views/desktop_certificate.xml @@ -0,0 +1,46 @@ + + + + + desktop.certificate + +
+ + + + + + + + + + + + +
+
+
+ + desktop.certificate + + + + + + + + + + + + +
diff --git a/setup/desktop_certificate/odoo/addons/desktop_certificate b/setup/desktop_certificate/odoo/addons/desktop_certificate new file mode 120000 index 000000000..6967cdf56 --- /dev/null +++ b/setup/desktop_certificate/odoo/addons/desktop_certificate @@ -0,0 +1 @@ +../../../../desktop_certificate \ No newline at end of file diff --git a/setup/desktop_certificate/setup.py b/setup/desktop_certificate/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/desktop_certificate/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)