diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6579307..814c2b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,8 @@ exclude: | ^README\.md$| # Library files can have extraneous formatting (even minimized) /static/(src/)?lib/| + # fixture directory can contain data used in test that is formatted in different manner + /tests/fixtures/| # Repos using Sphinx to generate docs don't need prettying ^docs/_templates/.*\.html$| # You don't usually want a bot to modify your legal texts diff --git a/data_encryption/README.rst b/data_encryption/README.rst new file mode 100644 index 0000000..837afa9 --- /dev/null +++ b/data_encryption/README.rst @@ -0,0 +1,115 @@ +=============== +Encryption data +=============== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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-OCA%2Fserver--env-lightgray.png?logo=github + :target: https://github.com/OCA/server-env/tree/12.0/data_encryption + :alt: OCA/server-env +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-env-12-0/server-env-12-0-data_encryption + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/254/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to encrypt and decrypt data. This module is not usable +by itself, it is a low level module which should work as a base for others. +An example is the module server_environment_data_encryption + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to edit the main configuration file +of your instance, and add a directive called ``running_env``. Commonly +used values are 'dev', 'test', 'production':: + + [options] + running_env=dev + + +You also need to set the encryption key(s). The main idea is to have different +encryption keys for your different environment, to avoid the possibility to retrieve +crucial information from the production environment in a developement environment, for instance. +So, if your running environment is 'dev':: + + [options] + encryption_key_dev=fyeMIx9XVPBBky5XZeLDxVc9dFKy7Uzas3AoyMarHPA= + +In the configuration file of your production environment, you may want to configure +all your other environments encryption key. This way, from production you can encrypt and decrypt +data for all environments. + +You can generate keys with python -c 'from cryptography.fernet import Fernet; print Fernet.generate_key()'. + +Known issues / Roadmap +====================== + +For now the encryption is dependent on the environment. It has been designed +to store the same kind of data with different values depending on the environement +(dev, preprod, prod...). +An improvement could be to split this in 2 modules. But the environment stuff +is not a big constraint. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Raphaël Reverdy +* Florian da Costa + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-env `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/data_encryption/__init__.py b/data_encryption/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/data_encryption/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/data_encryption/__manifest__.py b/data_encryption/__manifest__.py new file mode 100644 index 0000000..3655a5d --- /dev/null +++ b/data_encryption/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright <2019> Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Encryption data", + "summary": "Store accounts and credentials encrypted by environment", + "version": "14.0.1.0.0", + "development_status": "Alpha", + "category": "Tools", + "website": "https://github.com/OCA/server-env", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "external_dependencies": {"python": ["cryptography"]}, + "depends": ["base"], + "data": ["security/ir.model.access.csv"], +} diff --git a/data_encryption/i18n/data_encryption.pot b/data_encryption/i18n/data_encryption.pot new file mode 100644 index 0000000..38f2f21 --- /dev/null +++ b/data_encryption/i18n/data_encryption.pot @@ -0,0 +1,121 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * data_encryption +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: data_encryption +#: model:ir.model.fields,help:data_encryption.field_encrypted_data__environment +msgid "Concerned Odoo environment (prod, preprod...)" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__create_uid +msgid "Created by" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__create_date +msgid "Created on" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__display_name +msgid "Display Name" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__encrypted_data +msgid "Encrypted Data" +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:58 +#, python-format +msgid "Encrypted data can only be read as superuser" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__environment +msgid "Environment" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__id +msgid "ID" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data____last_update +msgid "Last Modified on" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__write_date +msgid "Last Updated on" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,field_description:data_encryption.field_encrypted_data__name +msgid "Name" +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:100 +#, python-format +msgid "No '%s' entry found in config file. Use a key similar to: %s" +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:87 +#, python-format +msgid "No environment found, please check your running_env entry in your config file." +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:46 +#, python-format +msgid "Password has been encrypted with a different key. Unless you can recover the previous key, this password is unreadable." +msgstr "" + +#. module: data_encryption +#: model:ir.model,name:data_encryption.model_encrypted_data +msgid "Store any encrypted data by environment" +msgstr "" + +#. module: data_encryption +#: model:ir.model.fields,help:data_encryption.field_encrypted_data__name +msgid "Technical name" +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:77 +#, python-format +msgid "The data you are trying to read are not in a json format" +msgstr "" + +#. module: data_encryption +#: sql_constraint:encrypted.data:0 +msgid "You can not store multiple encrypted data for the same record and environment" +msgstr "" + +#. module: data_encryption +#: code:addons/data_encryption/models/encrypted_data.py:119 +#, python-format +msgid "You can only encrypt data as superuser" +msgstr "" + diff --git a/data_encryption/models/__init__.py b/data_encryption/models/__init__.py new file mode 100644 index 0000000..da8f5b3 --- /dev/null +++ b/data_encryption/models/__init__.py @@ -0,0 +1 @@ +from . import encrypted_data diff --git a/data_encryption/models/encrypted_data.py b/data_encryption/models/encrypted_data.py new file mode 100644 index 0000000..d9dbc67 --- /dev/null +++ b/data_encryption/models/encrypted_data.py @@ -0,0 +1,145 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import json +import logging + +from odoo import api, fields, models +from odoo.exceptions import AccessError, ValidationError +from odoo.tools import ormcache +from odoo.tools.config import config +from odoo.tools.translate import _ + +_logger = logging.getLogger(__name__) + +try: + from cryptography.fernet import Fernet, InvalidToken +except ImportError as err: # pragma: no cover + _logger.debug(err) + + +class EncryptedData(models.Model): + """Model to store encrypted data by environment (prod, preprod...)""" + + _name = "encrypted.data" + _description = "Store any encrypted data by environment" + + name = fields.Char(required=True, readonly=True, index=True, help="Technical name") + environment = fields.Char( + required=True, + index=True, + help="Concerned Odoo environment (prod, preprod...)", + ) + encrypted_data = fields.Binary(attachment=False) + + _sql_constraints = [ + ( + "name_environment_uniq", + "unique (name, environment)", + "You can not store multiple encrypted data for the same record and \ + environment", + ) + ] + + def _decrypt_data(self, env): + self.ensure_one() + cipher = self._get_cipher(env) + try: + return cipher.decrypt(self.encrypted_data).decode() + except InvalidToken: + raise ValidationError( + _( + "Password has been encrypted with a different " + "key. Unless you can recover the previous key, " + "this password is unreadable." + ) + ) + + @api.model + @ormcache("self._uid", "name", "env") + def _encrypted_get(self, name, env=None): + if self.env.context.get("bin_size"): + self = self.with_context(bin_size=False) + if not self.env.su: + raise AccessError( + _("Encrypted data can only be read with suspended security (sudo)") + ) + if not env: + env = self._retrieve_env() + encrypted_rec = self.search([("name", "=", name), ("environment", "=", env)]) + if not encrypted_rec: + return None + return encrypted_rec._decrypt_data(env) + + @api.model + @ormcache("self._uid", "name", "env") + def _encrypted_read_json(self, name, env=None): + data = self._encrypted_get(name, env=env) + if not data: + return {} + try: + return json.loads(data) + except (ValueError, TypeError): + raise ValidationError( + _("The data you are trying to read are not in a json format") + ) + + @staticmethod + def _retrieve_env(): + """Return the current environment + Raise if none is found + """ + current = config.get("running_env", False) + if not current: + raise ValidationError( + _( + "No environment found, please check your running_env " + "entry in your config file." + ) + ) + return current + + @classmethod + def _get_cipher(cls, env): + """Return a cipher using the key of environment. + force_env = name of the env key. + Useful for encoding against one precise env + """ + key_name = "encryption_key_%s" % env + key_str = config.get(key_name) + if not key_str: + raise ValidationError( + _("No '%s' entry found in config file. " "Use a key similar to: %s") + % (key_name, Fernet.generate_key()) + ) + # key should be in bytes format + key = key_str.encode() + return Fernet(key) + + @api.model + def _encrypt_data(self, data, env): + cipher = self._get_cipher(env) + if not isinstance(data, bytes): + data = data.encode() + return cipher.encrypt(data or "") + + @api.model + def _encrypted_store(self, name, data, env=None): + if not self.env.su: + raise AccessError( + _("You can only encrypt data with suspended security (sudo)") + ) + if not env: + env = self._retrieve_env() + encrypted_data = self._encrypt_data(data, env) + existing_data = self.search([("name", "=", name), ("environment", "=", env)]) + if existing_data: + existing_data.write({"encrypted_data": encrypted_data}) + else: + self.create( + {"name": name, "environment": env, "encrypted_data": encrypted_data} + ) + self._encrypted_get.clear_cache(self) + self._encrypted_read_json.clear_cache(self) + + @api.model + def _encrypted_store_json(self, name, json_data, env=None): + return self._encrypted_store(name, json.dumps(json_data), env=env) diff --git a/data_encryption/readme/CONFIGURE.rst b/data_encryption/readme/CONFIGURE.rst new file mode 100644 index 0000000..69b4773 --- /dev/null +++ b/data_encryption/readme/CONFIGURE.rst @@ -0,0 +1,21 @@ +To configure this module, you need to edit the main configuration file +of your instance, and add a directive called ``running_env``. Commonly +used values are 'dev', 'test', 'production':: + + [options] + running_env=dev + + +You also need to set the encryption key(s). The main idea is to have different +encryption keys for your different environment, to avoid the possibility to retrieve +crucial information from the production environment in a developement environment, for instance. +So, if your running environment is 'dev':: + + [options] + encryption_key_dev=fyeMIx9XVPBBky5XZeLDxVc9dFKy7Uzas3AoyMarHPA= + +In the configuration file of your production environment, you may want to configure +all your other environments encryption key. This way, from production you can encrypt and decrypt +data for all environments. + +You can generate keys with python -c 'from cryptography.fernet import Fernet; print Fernet.generate_key()'. diff --git a/data_encryption/readme/CONTRIBUTORS.rst b/data_encryption/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..3017ba2 --- /dev/null +++ b/data_encryption/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Raphaël Reverdy +* Florian da Costa diff --git a/data_encryption/readme/DESCRIPTION.rst b/data_encryption/readme/DESCRIPTION.rst new file mode 100644 index 0000000..9c4dc15 --- /dev/null +++ b/data_encryption/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to encrypt and decrypt data. This module is not usable +by itself, it is a low level module which should work as a base for others. +An example is the module server_environment_data_encryption diff --git a/data_encryption/readme/ROADMAP.rst b/data_encryption/readme/ROADMAP.rst new file mode 100644 index 0000000..71a6a12 --- /dev/null +++ b/data_encryption/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +For now the encryption is dependent on the environment. It has been designed +to store the same kind of data with different values depending on the environement +(dev, preprod, prod...). +An improvement could be to split this in 2 modules. But the environment stuff +is not a big constraint. diff --git a/data_encryption/security/ir.model.access.csv b/data_encryption/security/ir.model.access.csv new file mode 100644 index 0000000..ddbd5f7 --- /dev/null +++ b/data_encryption/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_encrypted_data,access_encrypted_data,model_encrypted_data,base.group_system,0,0,0,0 diff --git a/data_encryption/static/description/icon.png b/data_encryption/static/description/icon.png new file mode 100644 index 0000000..3a0328b Binary files /dev/null and b/data_encryption/static/description/icon.png differ diff --git a/data_encryption/static/description/index.html b/data_encryption/static/description/index.html new file mode 100644 index 0000000..b832f77 --- /dev/null +++ b/data_encryption/static/description/index.html @@ -0,0 +1,460 @@ + + + + + + +Encryption data + + + +
+

Encryption data

+ + +

Alpha License: AGPL-3 OCA/server-env Translate me on Weblate Try me on Runbot

+

This module allows to encrypt and decrypt data. This module is not usable +by itself, it is a low level module which should work as a base for others. +An example is the module server_environment_data_encryption

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to edit the main configuration file +of your instance, and add a directive called running_env. Commonly +used values are ‘dev’, ‘test’, ‘production’:

+
+[options]
+running_env=dev
+
+

You also need to set the encryption key(s). The main idea is to have different +encryption keys for your different environment, to avoid the possibility to retrieve +crucial information from the production environment in a developement environment, for instance. +So, if your running environment is ‘dev’:

+
+[options]
+encryption_key_dev=fyeMIx9XVPBBky5XZeLDxVc9dFKy7Uzas3AoyMarHPA=
+
+

In the configuration file of your production environment, you may want to configure +all your other environments encryption key. This way, from production you can encrypt and decrypt +data for all environments.

+

You can generate keys with python -c ‘from cryptography.fernet import Fernet; print Fernet.generate_key()’.

+
+
+

Known issues / Roadmap

+

For now the encryption is dependent on the environment. It has been designed +to store the same kind of data with different values depending on the environement +(dev, preprod, prod…). +An improvement could be to split this in 2 modules. But the environment stuff +is not a big constraint.

+
+
+

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 smashing 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

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-env project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/data_encryption/tests/__init__.py b/data_encryption/tests/__init__.py new file mode 100644 index 0000000..d686b32 --- /dev/null +++ b/data_encryption/tests/__init__.py @@ -0,0 +1 @@ +from . import test_data_encrypt diff --git a/data_encryption/tests/common.py b/data_encryption/tests/common.py new file mode 100644 index 0000000..a57377d --- /dev/null +++ b/data_encryption/tests/common.py @@ -0,0 +1,36 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo.tests.common import TransactionCase +from odoo.tools.config import config + +_logger = logging.getLogger(__name__) + +try: + from cryptography.fernet import Fernet +except ImportError as err: # pragma: no cover + _logger.debug(err) + + +class CommonDataEncrypted(TransactionCase): + def setUp(self): + super().setUp() + + self.encrypted_data = self.env["encrypted.data"] + self.set_new_key_env("test") + self.old_running_env = config.get("running_env", "") + config["running_env"] = "test" + self.crypted_data_name = "test_model,1" + + def set_new_key_env(self, environment): + crypting_key = Fernet.generate_key() + # The key is encoded to bytes in the module, because in real life + # the key com from the config file and is not in a binary format. + # So we decode here to avoid having a special behavior because of + # the tests. + config["encryption_key_{}".format(environment)] = crypting_key.decode() + + def tearDown(self): + config["running_env"] = self.old_running_env + return super().tearDown() diff --git a/data_encryption/tests/test_data_encrypt.py b/data_encryption/tests/test_data_encrypt.py new file mode 100644 index 0000000..7f25053 --- /dev/null +++ b/data_encryption/tests/test_data_encrypt.py @@ -0,0 +1,92 @@ +# © 2016 Akretion Raphaël REVERDY +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo.exceptions import AccessError, ValidationError +from odoo.tools.config import config + +from .common import CommonDataEncrypted + +_logger = logging.getLogger(__name__) + +try: + from cryptography.fernet import Fernet +except ImportError as err: # pragma: no cover + _logger.debug(err) + + +class TestDataEncrypted(CommonDataEncrypted): + def test_store_data_no_superuser(self): + # only superuser can use this model + admin = self.env.ref("base.user_admin") + with self.assertRaises(AccessError): + self.encrypted_data.with_user(admin.id)._encrypted_store( + self.crypted_data_name, "My config" + ) + + def test_store_data_noenv_set(self): + config.pop("running_env", None) + with self.assertRaises(ValidationError): + self.encrypted_data.sudo()._encrypted_store( + self.crypted_data_name, "My config" + ) + + def test_store_data_nokey_set(self): + config.pop("encryption_key_test", None) + with self.assertRaises(ValidationError): + self.encrypted_data.sudo()._encrypted_store( + self.crypted_data_name, "My config" + ) + + def test_get_data_decrypted_and_cache(self): + self.encrypted_data.sudo()._encrypted_store("test_model,1", "My config") + data = self.encrypted_data.sudo()._encrypted_get(self.crypted_data_name) + self.assertEqual(data, "My config") + + # Test cache really depends on user (super user) else any user could + # access the data + admin = self.env.ref("base.user_admin") + with self.assertRaises(AccessError): + self.encrypted_data.with_user(admin)._encrypted_get(self.crypted_data_name) + + # Change value should invalidate cache + self.encrypted_data.sudo()._encrypted_store("test_model,1", "Other Config") + new_data = self.encrypted_data.sudo()._encrypted_get(self.crypted_data_name) + self.assertEqual(new_data, "Other Config") + + def test_get_data_wrong_key(self): + self.encrypted_data.sudo()._encrypted_store("test_model,1", "My config") + new_key = Fernet.generate_key() + config["encryption_key_test"] = new_key.decode() + with self.assertRaises(ValidationError): + self.encrypted_data.sudo()._encrypted_get(self.crypted_data_name) + + def test_get_empty_data(self): + empty_data = self.encrypted_data.sudo()._encrypted_get(self.crypted_data_name) + self.assertEqual(empty_data, None) + + def test_get_wrong_json(self): + self.encrypted_data.sudo()._encrypted_store(self.crypted_data_name, "config") + with self.assertRaises(ValidationError): + self.encrypted_data.sudo()._encrypted_read_json(self.crypted_data_name) + + def test_get_good_json(self): + self.encrypted_data.sudo()._encrypted_store_json( + self.crypted_data_name, {"key": "value"} + ) + data = self.encrypted_data.sudo()._encrypted_read_json(self.crypted_data_name) + self.assertEqual(data, {"key": "value"}) + + def test_get_empty_json(self): + data = self.encrypted_data.sudo()._encrypted_read_json(self.crypted_data_name) + self.assertEqual(data, {}) + + def test_get_data_with_bin_size_context(self): + self.encrypted_data.sudo()._encrypted_store(self.crypted_data_name, "test") + data = ( + self.encrypted_data.sudo() + .with_context(bin_size=True) + ._encrypted_get(self.crypted_data_name) + ) + self.assertEqual(data, "test") diff --git a/server_environment_data_encryption/README.rst b/server_environment_data_encryption/README.rst new file mode 100644 index 0000000..7b64980 --- /dev/null +++ b/server_environment_data_encryption/README.rst @@ -0,0 +1,113 @@ +================================== +Server Environment Data Encryption +================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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-OCA%2Fserver--env-lightgray.png?logo=github + :target: https://github.com/OCA/server-env/tree/12.0/server_environment_data_encryption + :alt: OCA/server-env +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-env-12-0/server-env-12-0-server_environment_data_encryption + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/254/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +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. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +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 + + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Florian da Costa +* Sébastien Beau +* Benoît Guillot + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-env `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. 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..c7e8e22 --- /dev/null +++ b/server_environment_data_encryption/__manifest__.py @@ -0,0 +1,12 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Server Environment Data Encryption", + "version": "14.0.1.0.0", + "development_status": "Alpha", + "category": "Tools", + "website": "https://github.com/OCA/server-env", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": ["server_environment", "data_encryption"], +} diff --git a/server_environment_data_encryption/i18n/server_environment_data_encryption.pot b/server_environment_data_encryption/i18n/server_environment_data_encryption.pot new file mode 100644 index 0000000..0acf17b --- /dev/null +++ b/server_environment_data_encryption/i18n/server_environment_data_encryption.pot @@ -0,0 +1,38 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * server_environment_data_encryption +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: server_environment_data_encryption +#: code:addons/server_environment_data_encryption/models/server_env_mixin.py:74 +#, python-format +msgid "Define values for " +msgstr "" + +#. module: server_environment_data_encryption +#: model:ir.model,name:server_environment_data_encryption.model_server_env_mixin +msgid "Mixin to add server environment in existing models" +msgstr "" + +#. module: server_environment_data_encryption +#: code:addons/server_environment_data_encryption/models/server_env_mixin.py:86 +#, python-format +msgid "Modify values for {} environment" +msgstr "" + +#. module: server_environment_data_encryption +#: code:addons/server_environment_data_encryption/models/server_env_mixin.py:137 +#, python-format +msgid "you need to define the running_env entry in your odoo configuration file" +msgstr "" + 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..c008577 --- /dev/null +++ b/server_environment_data_encryption/models/server_env_mixin.py @@ -0,0 +1,159 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import json +import logging + +from lxml import etree + +from odoo import _, api, models +from odoo.exceptions import ValidationError +from odoo.tools.config import config + +_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 = "{},{}".format(self._name, self.id) + env = self.env.context.get("environment", None) + vals = ( + self.env["encrypted.data"] + .sudo() + ._encrypted_read_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 = "{},{}".format(record._name, record.id) + 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._encrypted_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 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", []): + 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): + # TODO we could use a qweb template here + 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") + field.set("modifiers", json.dumps({"readonly": True})) + + 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") + # 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( + _( + "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..37875fa --- /dev/null +++ b/server_environment_data_encryption/readme/CONFIGURE.rst @@ -0,0 +1,23 @@ +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/static/description/icon.png b/server_environment_data_encryption/static/description/icon.png new file mode 100644 index 0000000..3a0328b Binary files /dev/null and b/server_environment_data_encryption/static/description/icon.png differ diff --git a/server_environment_data_encryption/static/description/index.html b/server_environment_data_encryption/static/description/index.html new file mode 100644 index 0000000..5bd6658 --- /dev/null +++ b/server_environment_data_encryption/static/description/index.html @@ -0,0 +1,459 @@ + + + + + + +Server Environment Data Encryption + + + +
+

Server Environment Data Encryption

+ + +

Alpha License: AGPL-3 OCA/server-env Translate me on Weblate Try me on Runbot

+

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.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

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
+
+
+
+

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 smashing 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

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-env project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + 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..11f2eb7 --- /dev/null +++ b/server_environment_data_encryption/tests/fixtures/base.xml @@ -0,0 +1,22 @@ +
+
+
+ + + + + + + +
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..d415f70 --- /dev/null +++ b/server_environment_data_encryption/tests/test_server_environment_data_encrypt.py @@ -0,0 +1,33 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from pathlib import Path + +from odoo.addons.data_encryption.tests.common import CommonDataEncrypted + + +class TestServerEnvDataEncrypted(CommonDataEncrypted): + 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.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.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) diff --git a/setup/data_encryption/odoo/addons/data_encryption b/setup/data_encryption/odoo/addons/data_encryption new file mode 120000 index 0000000..2aee058 --- /dev/null +++ b/setup/data_encryption/odoo/addons/data_encryption @@ -0,0 +1 @@ +../../../../data_encryption \ No newline at end of file diff --git a/setup/data_encryption/setup.py b/setup/data_encryption/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/data_encryption/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/server_environment_data_encryption/odoo/addons/server_environment_data_encryption b/setup/server_environment_data_encryption/odoo/addons/server_environment_data_encryption new file mode 120000 index 0000000..8052eed --- /dev/null +++ b/setup/server_environment_data_encryption/odoo/addons/server_environment_data_encryption @@ -0,0 +1 @@ +../../../../server_environment_data_encryption \ No newline at end of file diff --git a/setup/server_environment_data_encryption/setup.py b/setup/server_environment_data_encryption/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/server_environment_data_encryption/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)