From 4d1623951870747204deed66bfc352c082b86b1f Mon Sep 17 00:00:00 2001 From: Maksym Yankin Date: Thu, 9 Jul 2026 17:12:13 +0300 Subject: [PATCH 1/2] [19.0][IMP] webservice: neutralize webservice backend credentials --- webservice/README.rst | 4 ++ webservice/data/neutralize.sql | 9 +++++ webservice/readme/CONFIGURE.md | 4 ++ webservice/static/description/index.html | 3 ++ webservice/tests/__init__.py | 1 + webservice/tests/test_neutralize.py | 49 ++++++++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 webservice/data/neutralize.sql create mode 100644 webservice/tests/test_neutralize.py diff --git a/webservice/README.rst b/webservice/README.rst index ffdcd4cc..5322eff7 100644 --- a/webservice/README.rst +++ b/webservice/README.rst @@ -84,6 +84,10 @@ endpoint can be configured as: Client Auth Header = Authorization Client Auth Value = SSWS +When a database is neutralized, stored webservice backend credentials +(username, password, API key, OAuth2 client id/secret/token, custom +OAuth2 auth header value) are cleared. + Bug Tracker =========== diff --git a/webservice/data/neutralize.sql b/webservice/data/neutralize.sql new file mode 100644 index 00000000..264b1b17 --- /dev/null +++ b/webservice/data/neutralize.sql @@ -0,0 +1,9 @@ +-- remove webservice backend credentials +UPDATE webservice_backend + SET username = NULL, + password = NULL, + api_key = NULL, + oauth2_clientid = NULL, + oauth2_client_secret = NULL, + oauth2_client_auth_value = NULL, + oauth2_token = NULL; diff --git a/webservice/readme/CONFIGURE.md b/webservice/readme/CONFIGURE.md index 5285301d..4cd9ccc6 100644 --- a/webservice/readme/CONFIGURE.md +++ b/webservice/readme/CONFIGURE.md @@ -29,3 +29,7 @@ configured as: Client Authentication = Custom Authorization header Client Auth Header = Authorization Client Auth Value = SSWS + +When a database is neutralized, stored webservice backend credentials +(username, password, API key, OAuth2 client id/secret/token, custom +OAuth2 auth header value) are cleared. diff --git a/webservice/static/description/index.html b/webservice/static/description/index.html index 5326d571..2797d454 100644 --- a/webservice/static/description/index.html +++ b/webservice/static/description/index.html @@ -435,6 +435,9 @@

Example: custom Authorization hea Client Auth Header = Authorization Client Auth Value = SSWS <token> +

When a database is neutralized, stored webservice backend credentials +(username, password, API key, OAuth2 client id/secret/token, custom +OAuth2 auth header value) are cleared.

diff --git a/webservice/tests/__init__.py b/webservice/tests/__init__.py index 70d7ce42..88a07b8f 100644 --- a/webservice/tests/__init__.py +++ b/webservice/tests/__init__.py @@ -1,3 +1,4 @@ from . import test_oauth2 from . import test_webservice from . import test_utils +from . import test_neutralize diff --git a/webservice/tests/test_neutralize.py b/webservice/tests/test_neutralize.py new file mode 100644 index 00000000..e1e10959 --- /dev/null +++ b/webservice/tests/test_neutralize.py @@ -0,0 +1,49 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.modules import neutralize +from odoo.tests import tagged + +from .common import CommonWebService + + +@tagged("neutralize") +class TestWebserviceNeutralize(CommonWebService): + @classmethod + def _setup_records(cls): + res = super()._setup_records() + # `auth_type="none"` skips the `_check_auth_type` constraint so all + # credential fields can be populated at once regardless of which + # auth method would normally require them. + cls.backend = cls.env["webservice.backend"].create( + { + "name": "Neutralize WebService", + "protocol": "http", + "url": "https://localhost.demo.odoo/", + "tech_name": "neutralize_ws", + "auth_type": "none", + "username": "secret-user", + "password": "secret-password", + "api_key": "secret-api-key", + "oauth2_clientid": "secret-client-id", + "oauth2_client_secret": "secret-client-secret", + "oauth2_client_auth_value": "secret-header-value", + "oauth2_token": '{"access_token": "secret-token"}', + } + ) + return res + + def test_neutralize_removes_webservice_credentials(self): + """Test database neutralization clears stored webservice backend credentials.""" + installed_modules = neutralize.get_installed_modules(self.cr) + queries = neutralize.get_neutralization_queries(installed_modules) + for query in queries: + self.cr.execute(query) + self.backend.invalidate_recordset() + self.assertFalse(self.backend.username) + self.assertFalse(self.backend.password) + self.assertFalse(self.backend.api_key) + self.assertFalse(self.backend.oauth2_clientid) + self.assertFalse(self.backend.oauth2_client_secret) + self.assertFalse(self.backend.oauth2_client_auth_value) + self.assertFalse(self.backend.oauth2_token) From 8da616834d3f3834a1161b2258b07faca2f7489b Mon Sep 17 00:00:00 2001 From: Maksym Yankin Date: Thu, 9 Jul 2026 18:25:24 +0300 Subject: [PATCH 2/2] [19.0][IMP] webservice_server_env: neutralize webservice backend credentials If both webservice and server_environment are installed we also need neutralization for webservice_server_env so the plain columns nulled by webservice/data/neutralize.sql can be properly cleared --- webservice_server_env/README.rst | 8 ++++ webservice_server_env/data/neutralize.sql | 16 +++++++ webservice_server_env/readme/CONFIGURE.md | 3 ++ .../static/description/index.html | 28 +++++++---- webservice_server_env/tests/__init__.py | 1 + .../tests/test_neutralize.py | 47 +++++++++++++++++++ 6 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 webservice_server_env/data/neutralize.sql create mode 100644 webservice_server_env/readme/CONFIGURE.md create mode 100644 webservice_server_env/tests/test_neutralize.py diff --git a/webservice_server_env/README.rst b/webservice_server_env/README.rst index e6173a9f..8c7ec4cd 100644 --- a/webservice_server_env/README.rst +++ b/webservice_server_env/README.rst @@ -40,6 +40,14 @@ Webservice addon. .. contents:: :local: +Configuration +============= + +When a database is neutralized, webservice backend credentials +(username, password, API key, OAuth2 client id/secret, custom OAuth2 +auth header value) stored as server environment defaults are cleared as +well. + Bug Tracker =========== diff --git a/webservice_server_env/data/neutralize.sql b/webservice_server_env/data/neutralize.sql new file mode 100644 index 00000000..ae619e29 --- /dev/null +++ b/webservice_server_env/data/neutralize.sql @@ -0,0 +1,16 @@ +-- remove webservice backend credentials stored as server environment defaults +-- (this module turns username/password/api_key/oauth2_clientid/oauth2_client_secret +-- into non-stored fields backed by the server_env_defaults JSON column +-- this neutralization is needed if both webservice and server_environment are installed, +-- so the plain columns nulled by webservice/data/neutralize.sql can be properly cleared) +UPDATE webservice_backend + SET server_env_defaults = ( + server_env_defaults::jsonb + - 'x_username_env_default' + - 'x_password_env_default' + - 'x_api_key_env_default' + - 'x_oauth2_clientid_env_default' + - 'x_oauth2_client_secret_env_default' + - 'x_oauth2_client_auth_value_env_default' + )::text + WHERE server_env_defaults IS NOT NULL; diff --git a/webservice_server_env/readme/CONFIGURE.md b/webservice_server_env/readme/CONFIGURE.md new file mode 100644 index 00000000..6c80484f --- /dev/null +++ b/webservice_server_env/readme/CONFIGURE.md @@ -0,0 +1,3 @@ +When a database is neutralized, webservice backend credentials (username, +password, API key, OAuth2 client id/secret, custom OAuth2 auth header +value) stored as server environment defaults are cleared as well. diff --git a/webservice_server_env/static/description/index.html b/webservice_server_env/static/description/index.html index 0117c49e..c5337b16 100644 --- a/webservice_server_env/static/description/index.html +++ b/webservice_server_env/static/description/index.html @@ -380,17 +380,25 @@

WebService Server Environment

Table of contents

+
+

Configuration

+

When a database is neutralized, webservice backend credentials +(username, password, API key, OAuth2 client id/secret, custom OAuth2 +auth header value) stored as server environment defaults are cleared as +well.

+
-

Bug Tracker

+

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 @@ -398,23 +406,23 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Creu Blanca
  • Camptocamp
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association diff --git a/webservice_server_env/tests/__init__.py b/webservice_server_env/tests/__init__.py index 38fb5cf0..e228ba19 100644 --- a/webservice_server_env/tests/__init__.py +++ b/webservice_server_env/tests/__init__.py @@ -1 +1,2 @@ from . import test_oauth2 +from . import test_neutralize diff --git a/webservice_server_env/tests/test_neutralize.py b/webservice_server_env/tests/test_neutralize.py new file mode 100644 index 00000000..906630e0 --- /dev/null +++ b/webservice_server_env/tests/test_neutralize.py @@ -0,0 +1,47 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.modules import neutralize +from odoo.tests import tagged + +from odoo.addons.webservice.tests.common import CommonWebService + + +@tagged("neutralize") +class TestWebserviceServerEnvNeutralize(CommonWebService): + @classmethod + def _setup_records(cls): + res = super()._setup_records() + # No `SERVER_ENV_CONFIG` section is defined for this tech_name, so + # these credentials are stored as env-default values inside + # `server_env_defaults` instead of the (now unused) plain columns. + cls.backend = cls.env["webservice.backend"].create( + { + "name": "Neutralize WebService Server Env", + "protocol": "http", + "url": "https://localhost.demo.odoo/", + "tech_name": "neutralize_server_env_ws", + "auth_type": "none", + "username": "secret-user", + "password": "secret-password", + "api_key": "secret-api-key", + "oauth2_clientid": "secret-client-id", + "oauth2_client_secret": "secret-client-secret", + "oauth2_client_auth_value": "secret-header-value", + } + ) + return res + + def test_neutralize_removes_server_env_default_credentials(self): + """Test neutralization clears credentials stored as server-env defaults.""" + installed_modules = neutralize.get_installed_modules(self.cr) + queries = neutralize.get_neutralization_queries(installed_modules) + for query in queries: + self.cr.execute(query) + self.backend.invalidate_recordset() + self.assertFalse(self.backend.username) + self.assertFalse(self.backend.password) + self.assertFalse(self.backend.api_key) + self.assertFalse(self.backend.oauth2_clientid) + self.assertFalse(self.backend.oauth2_client_secret) + self.assertFalse(self.backend.oauth2_client_auth_value)