From 217b8eec1116776f6d72e02b207f6fbe2fab079c Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 12 Apr 2019 20:02:55 +0200 Subject: [PATCH 1/4] Add module server_environment_data_encryption --- .../__init__.py | 1 + .../__manifest__.py | 13 ++ .../models/__init__.py | 1 + .../models/server_env_mixin.py | 165 ++++++++++++++++++ .../readme/CONFIGURE.rst | 24 +++ .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 6 + .../tests/__init__.py | 1 + .../tests/fixtures/base.xml | 15 ++ .../tests/fixtures/res1.xml | 21 +++ .../tests/fixtures/res2.xml | 21 +++ .../test_server_environment_data_encrypt.py | 36 ++++ 12 files changed, 307 insertions(+) create mode 100644 server_environment_data_encryption/__init__.py create mode 100644 server_environment_data_encryption/__manifest__.py create mode 100644 server_environment_data_encryption/models/__init__.py create mode 100644 server_environment_data_encryption/models/server_env_mixin.py create mode 100644 server_environment_data_encryption/readme/CONFIGURE.rst create mode 100644 server_environment_data_encryption/readme/CONTRIBUTORS.rst create mode 100644 server_environment_data_encryption/readme/DESCRIPTION.rst create mode 100644 server_environment_data_encryption/tests/__init__.py create mode 100644 server_environment_data_encryption/tests/fixtures/base.xml create mode 100644 server_environment_data_encryption/tests/fixtures/res1.xml create mode 100644 server_environment_data_encryption/tests/fixtures/res2.xml create mode 100644 server_environment_data_encryption/tests/test_server_environment_data_encrypt.py diff --git a/server_environment_data_encryption/__init__.py b/server_environment_data_encryption/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/server_environment_data_encryption/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/server_environment_data_encryption/__manifest__.py b/server_environment_data_encryption/__manifest__.py new file mode 100644 index 0000000..c1452cc --- /dev/null +++ b/server_environment_data_encryption/__manifest__.py @@ -0,0 +1,13 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Server Environment Data Encryption", + "version": "12.0.0.0.1", + "category": "Tools", + "website": "https://akretion.com/", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["server_environment", "data_encryption"], + "data": [], +} diff --git a/server_environment_data_encryption/models/__init__.py b/server_environment_data_encryption/models/__init__.py new file mode 100644 index 0000000..6bd869a --- /dev/null +++ b/server_environment_data_encryption/models/__init__.py @@ -0,0 +1 @@ +from . import server_env_mixin diff --git a/server_environment_data_encryption/models/server_env_mixin.py b/server_environment_data_encryption/models/server_env_mixin.py new file mode 100644 index 0000000..5579c4a --- /dev/null +++ b/server_environment_data_encryption/models/server_env_mixin.py @@ -0,0 +1,165 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +from odoo import api, models, _ +from odoo.exceptions import ValidationError +from odoo.tools.config import config +from lxml import etree +from odoo.osv.orm import setup_modifiers + + +_logger = logging.getLogger(__name__) + + +class ServerEnvMixin(models.AbstractModel): + _inherit = "server.env.mixin" + + def _compute_server_env_from_default(self, field_name, options): + "First return database encrypted value then default value" + self.ensure_one() + encrypted_data_name = "%s,%s" % (self._name, self.id) + env = self.env.context.get("environment", None) + vals = ( + self.env["encrypted.data"] + .sudo() + ._get_json(encrypted_data_name, env=env) + ) + if vals.get(field_name): + self[field_name] = vals[field_name] + else: + return super()._compute_server_env_from_default( + field_name, options + ) + + def _inverse_server_env(self, field_name): + """ + When this module is installed, we store values into encrypted data + env instead of a default field in database (not env dependent). + """ + is_editable_field = self._server_env_is_editable_fieldname(field_name) + encrypted_data_obj = self.env["encrypted.data"].sudo() + env = self.env.context.get("environment", None) + for record in self: + if record[is_editable_field]: + encrypted_data_name = "%s,%s" % (record._name, record.id) + values = encrypted_data_obj._get_json( + encrypted_data_name, env=env + ) + new_val = {field_name: record[field_name]} + values.update(new_val) + encrypted_data_obj._store_json( + encrypted_data_name, values, env=env + ) + + def action_change_env_data_encrypted_fields(self): + action_id = self.env.context.get("params", {}).get("action") + if not action_id: + # We don't know which action we are using... take one random. + action_id = self.env['ir.actions.act_window'].search( + [('res_model', '=', self._name)], limit=1).id + action = self.env["ir.actions.act_window"].browse(action_id).read()[0] + action["view_mode"] = "form" + action["res_id"] = self.id + views_form = [] + for view_id, view_type in action.get("views", []): + if view_type == "form": + views_form.append((view_id, view_type)) + action["views"] = views_form + return action + + def _get_extra_environment_info_div(self, current_env, extra_envs): + button_div = "
" + button_string = _("Define values for ") + for environment in extra_envs: + button = """ +
" + alert_string = _("Modify values for {} environment").format( + current_env + ) + alert_type = ( + current_env == config.get("running_env") + and "alert-info" + or "alert-warning" + ) + elem = etree.fromstring( + """ +
+
+ {} +
+ {} +
+ """.format( + alert_type, alert_string, button_div + ) + ) + return elem + + def _set_readonly_form_view(self, doc): + for field in doc.iter("field"): + env_fields = self._server_env_fields.keys() + field_name = field.get("name") + if field_name in env_fields: + continue + field.set("readonly", "1") + setup_modifiers(field, self.fields_get(field_name)) + + def _update_form_view_from_env(self, arch, view_type): + if view_type != "form": + return arch + current_env = self.env.context.get("environment") or config.get( + "running_env" + ) + other_environments = [ + key[15:] + for key, val in config.options.items() + if key.startswith("encryption_key_") + and val + and key[15:] != current_env + ] + + if not current_env: + raise ValidationError( + _( + "you need to define the running_env entry in your odoo " + "configuration file" + ) + ) + doc = etree.XML(arch) + node = doc.xpath("//sheet") + if node: + node = node[0] + elem = self._get_extra_environment_info_div( + current_env, other_environments + ) + node.insert(0, elem) + + if current_env != config.get("running_env"): + self._set_readonly_form_view(doc) + arch = etree.tostring(doc, pretty_print=True, encoding="unicode") + else: + _logger.error( + "Missing sheet for form view on object {}".format(self._name) + ) + return arch + + @api.model + def fields_view_get( + self, view_id=None, view_type="form", toolbar=False, submenu=False + ): + res = super().fields_view_get( + view_id=view_id, + view_type=view_type, + toolbar=toolbar, + submenu=submenu, + ) + res["arch"] = self._update_form_view_from_env(res["arch"], view_type) + return res diff --git a/server_environment_data_encryption/readme/CONFIGURE.rst b/server_environment_data_encryption/readme/CONFIGURE.rst new file mode 100644 index 0000000..9d0ad46 --- /dev/null +++ b/server_environment_data_encryption/readme/CONFIGURE.rst @@ -0,0 +1,24 @@ +In order to use this module properly, each environment should have their own encryption key +and the production environment should have the keys of all environments. + +Example : +Development environment :: + + [options] + running_env=dev + encryption_key_dev=XXX + +Pre-production environment :: + + [options] + running_env=preprod + encryption_key_preprod=YYY + +Production environment :: + + [options] + running_env=prod + encryption_key_dev=XXX + encryption_key_preprod=YYY + encryption_key_prod=ZZZ + diff --git a/server_environment_data_encryption/readme/CONTRIBUTORS.rst b/server_environment_data_encryption/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..86340ec --- /dev/null +++ b/server_environment_data_encryption/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Florian da Costa +* Sébastien Beau +* Benoît Guillot diff --git a/server_environment_data_encryption/readme/DESCRIPTION.rst b/server_environment_data_encryption/readme/DESCRIPTION.rst new file mode 100644 index 0000000..ebf4147 --- /dev/null +++ b/server_environment_data_encryption/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module changes a little the behavior of server_environment modules. +When Odoo does not find the value of the field in the configuration file, +it will fallback on a Odoo encrypted field instead. +Also it allows you +to configure the environment dependent fields for all your environments +from the production server. diff --git a/server_environment_data_encryption/tests/__init__.py b/server_environment_data_encryption/tests/__init__.py new file mode 100644 index 0000000..83ac8b8 --- /dev/null +++ b/server_environment_data_encryption/tests/__init__.py @@ -0,0 +1 @@ +from . import test_server_environment_data_encrypt diff --git a/server_environment_data_encryption/tests/fixtures/base.xml b/server_environment_data_encryption/tests/fixtures/base.xml new file mode 100644 index 0000000..524c959 --- /dev/null +++ b/server_environment_data_encryption/tests/fixtures/base.xml @@ -0,0 +1,15 @@ +
+
+
+ + + + + + + +
diff --git a/server_environment_data_encryption/tests/fixtures/res1.xml b/server_environment_data_encryption/tests/fixtures/res1.xml new file mode 100644 index 0000000..7ca515a --- /dev/null +++ b/server_environment_data_encryption/tests/fixtures/res1.xml @@ -0,0 +1,21 @@ +
+
+
+ +
+
+ Modify values for test environment +
+
+
+
+ + + + +
+
diff --git a/server_environment_data_encryption/tests/fixtures/res2.xml b/server_environment_data_encryption/tests/fixtures/res2.xml new file mode 100644 index 0000000..032e108 --- /dev/null +++ b/server_environment_data_encryption/tests/fixtures/res2.xml @@ -0,0 +1,21 @@ +
+
+
+ +
+
+ Modify values for prod environment +
+
+
+
+ + + + +
+
diff --git a/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py b/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py new file mode 100644 index 0000000..44be573 --- /dev/null +++ b/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py @@ -0,0 +1,36 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.addons.data_encryption.tests.common import CommonDataEncrypted +from pathlib import Path + + +class TestServerEnvDataEncrypted(CommonDataEncrypted): + + # def test_store_data_no_superuser(self): + # self.env['server.env.mixin']._inverse_server_env('test') + # pass + + def test_dynamic_view_current_env(self): + self.maxDiff = None + self.set_new_key_env("prod") + self.set_new_key_env("preprod") + mixin_obj = self.env["server.env.mixin"] + base_path = Path(__file__).parent / "fixtures" / "base.xml" + xml = base_path.read_text() + res_xml = mixin_obj._update_form_view_from_env(xml, "form") + expected_xml_path = Path(__file__).parent / "fixtures" / "res1.xml" + expected_xml = expected_xml_path.read_text() + self.assertEqual(res_xml, expected_xml) + + def test_dynamic_view_other_env(self): + self.set_new_key_env("prod") + self.set_new_key_env("preprod") + mixin_obj = self.env["server.env.mixin"] + base_path = Path(__file__).parent / "fixtures" / "base.xml" + xml = base_path.read_text() + res_xml = mixin_obj.with_context( + environment="prod" + )._update_form_view_from_env(xml, "form") + expected_xml_path = Path(__file__).parent / "fixtures" / "res2.xml" + expected_xml = expected_xml_path.read_text() + self.assertEqual(res_xml, expected_xml) From d7426b969a032c8db8e687a53be76cd426728c8d Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 4 Oct 2019 19:57:19 +0200 Subject: [PATCH 2/4] [REF] Adapt method name after change is depency --- server_environment_data_encryption/__manifest__.py | 3 ++- .../models/server_env_mixin.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server_environment_data_encryption/__manifest__.py b/server_environment_data_encryption/__manifest__.py index c1452cc..3d4d248 100644 --- a/server_environment_data_encryption/__manifest__.py +++ b/server_environment_data_encryption/__manifest__.py @@ -1,7 +1,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Server Environment Data Encryption", - "version": "12.0.0.0.1", + "version": "12.0.1.0.0", + "development_status": 'Alpha', "category": "Tools", "website": "https://akretion.com/", "author": "Akretion, Odoo Community Association (OCA)", diff --git a/server_environment_data_encryption/models/server_env_mixin.py b/server_environment_data_encryption/models/server_env_mixin.py index 5579c4a..8f43799 100644 --- a/server_environment_data_encryption/models/server_env_mixin.py +++ b/server_environment_data_encryption/models/server_env_mixin.py @@ -22,7 +22,7 @@ class ServerEnvMixin(models.AbstractModel): vals = ( self.env["encrypted.data"] .sudo() - ._get_json(encrypted_data_name, env=env) + ._encrypted_read_json(encrypted_data_name, env=env) ) if vals.get(field_name): self[field_name] = vals[field_name] @@ -42,12 +42,12 @@ class ServerEnvMixin(models.AbstractModel): for record in self: if record[is_editable_field]: encrypted_data_name = "%s,%s" % (record._name, record.id) - values = encrypted_data_obj._get_json( + values = encrypted_data_obj._encrypted_read_json( encrypted_data_name, env=env ) new_val = {field_name: record[field_name]} values.update(new_val) - encrypted_data_obj._store_json( + encrypted_data_obj._encrypted_store_json( encrypted_data_name, values, env=env ) From e0949fb9d21d2372a8629b5373165735d0cb54e9 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 4 Oct 2019 20:34:06 +0200 Subject: [PATCH 3/4] [FIX] Make sure the generated view is always the same and the order of the button does not change randomly --- .../models/server_env_mixin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server_environment_data_encryption/models/server_env_mixin.py b/server_environment_data_encryption/models/server_env_mixin.py index 8f43799..d6e16ed 100644 --- a/server_environment_data_encryption/models/server_env_mixin.py +++ b/server_environment_data_encryption/models/server_env_mixin.py @@ -118,13 +118,17 @@ class ServerEnvMixin(models.AbstractModel): current_env = self.env.context.get("environment") or config.get( "running_env" ) - other_environments = [ + # Important to keep this list sorted. It makes sure the button to + # switch environment will always be in the same order. (more user + # friendly) and the test would fail without it as the order could + # change randomly and the view would then also change randomly + other_environments = sorted([ key[15:] for key, val in config.options.items() if key.startswith("encryption_key_") and val and key[15:] != current_env - ] + ]) if not current_env: raise ValidationError( From e55d5a314e0f79d57f234fd4e586fd3d1cd56b70 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 23 Oct 2019 16:33:55 +0200 Subject: [PATCH 4/4] fixup! Add module server_environment_data_encryption --- server_environment_data_encryption/__manifest__.py | 4 +--- .../models/server_env_mixin.py | 14 ++++++++------ .../tests/test_server_environment_data_encrypt.py | 4 ---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/server_environment_data_encryption/__manifest__.py b/server_environment_data_encryption/__manifest__.py index 3d4d248..53ccd13 100644 --- a/server_environment_data_encryption/__manifest__.py +++ b/server_environment_data_encryption/__manifest__.py @@ -4,11 +4,9 @@ "version": "12.0.1.0.0", "development_status": 'Alpha', "category": "Tools", - "website": "https://akretion.com/", + "website": "https://github.com/OCA/server-env", "author": "Akretion, Odoo Community Association (OCA)", "license": "AGPL-3", - "application": False, "installable": True, "depends": ["server_environment", "data_encryption"], - "data": [], } diff --git a/server_environment_data_encryption/models/server_env_mixin.py b/server_environment_data_encryption/models/server_env_mixin.py index d6e16ed..d969793 100644 --- a/server_environment_data_encryption/models/server_env_mixin.py +++ b/server_environment_data_encryption/models/server_env_mixin.py @@ -15,7 +15,7 @@ class ServerEnvMixin(models.AbstractModel): _inherit = "server.env.mixin" def _compute_server_env_from_default(self, field_name, options): - "First return database encrypted value then default value" + """ First return database encrypted value then default value """ self.ensure_one() encrypted_data_name = "%s,%s" % (self._name, self.id) env = self.env.context.get("environment", None) @@ -54,11 +54,12 @@ class ServerEnvMixin(models.AbstractModel): def action_change_env_data_encrypted_fields(self): action_id = self.env.context.get("params", {}).get("action") if not action_id: - # We don't know which action we are using... take one random. - action_id = self.env['ir.actions.act_window'].search( - [('res_model', '=', self._name)], limit=1).id - action = self.env["ir.actions.act_window"].browse(action_id).read()[0] - action["view_mode"] = "form" + # We don't know which action we are using... take default one + action = self.get_formview_action() + else: + action = self.env["ir.actions.act_window"].browse( + action_id).read()[0] + action["view_mode"] = "form" action["res_id"] = self.id views_form = [] for view_id, view_type in action.get("views", []): @@ -68,6 +69,7 @@ class ServerEnvMixin(models.AbstractModel): return action def _get_extra_environment_info_div(self, current_env, extra_envs): + # TODO we could use a qweb template here button_div = "
" button_string = _("Define values for ") for environment in extra_envs: diff --git a/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py b/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py index 44be573..4194626 100644 --- a/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py +++ b/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py @@ -6,10 +6,6 @@ from pathlib import Path class TestServerEnvDataEncrypted(CommonDataEncrypted): - # def test_store_data_no_superuser(self): - # self.env['server.env.mixin']._inverse_server_env('test') - # pass - def test_dynamic_view_current_env(self): self.maxDiff = None self.set_new_key_env("prod")