diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..da9f588b --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-server_environment @ git+https://github.com/OCA/server-env.git@refs/pull/288/head#subdirectory=server_environment diff --git a/webservice/models/webservice_backend.py b/webservice/models/webservice_backend.py index e7301306..3b7f6916 100644 --- a/webservice/models/webservice_backend.py +++ b/webservice/models/webservice_backend.py @@ -167,6 +167,30 @@ def _valid_field_parameter(self, field, name): extra_params = ("auth_type",) return name in extra_params or super()._valid_field_parameter(field, name) + @api.onchange("auth_type") + def _onchange_auth_type(self): + # Keep `oauth2_flow` in sync in the UI as the user edits `auth_type`, + # regardless of whether `server_environment` is installed (see + # `create`/`write` below for the same guarantee on any other write). + if self.auth_type != "oauth2": + self.oauth2_flow = False + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + records.filtered( + lambda r: r.auth_type != "oauth2" and r.oauth2_flow + ).oauth2_flow = False + return records + + def write(self, vals): + res = super().write(vals) + if "auth_type" in vals: + self.filtered( + lambda r: r.auth_type != "oauth2" and r.oauth2_flow + ).oauth2_flow = False + return res + def call(self, method, *args, **kwargs): _logger.debug("backend %s: call %s %s %s", self.name, method, args, kwargs) response = getattr(self._get_adapter(), method)(*args, **kwargs) diff --git a/webservice/tests/test_oauth2.py b/webservice/tests/test_oauth2.py index 388d1548..1288cda8 100644 --- a/webservice/tests/test_oauth2.py +++ b/webservice/tests/test_oauth2.py @@ -374,3 +374,64 @@ def test_fetch_token_from_auth(self): json.loads(responses.calls[0].response.content.decode())["access_token"], ) self.assertEqual("cool_token", token["access_token"]) + + +class TestWebServiceOauth2FlowReset(CommonWebService): + """``oauth2_flow`` must be reset on any write, not only via the UI. + + This is a plain ORM-level guarantee independent of ``server_environment`` + (see ``webservice_server_env`` for the extra guarantee that applies when + that module is installed). + """ + + @classmethod + def _setup_records(cls): + res = super()._setup_records() + cls.url = "https://localhost.demo.odoo/" + cls.webservice = cls.env["webservice.backend"].create( + { + "name": "WebService OAuth2", + "tech_name": "test_oauth2_reset", + "auth_type": "oauth2", + "protocol": "http", + "url": cls.url, + "oauth2_flow": "backend_application", + "content_type": "application/xml", + "oauth2_clientid": "some_client_id", + "oauth2_client_secret": "shh_secret", + "oauth2_token_url": f"{cls.url}oauth2/token", + "oauth2_audience": cls.url, + } + ) + return res + + def test_write_resets_oauth2_flow_when_auth_type_changes(self): + self.webservice.write({"auth_type": "none"}) + self.assertFalse(self.webservice.oauth2_flow) + + def test_write_keeps_oauth2_flow_when_auth_type_stays_oauth2(self): + self.webservice.write({"oauth2_client_secret": "new_secret"}) + self.assertEqual(self.webservice.oauth2_flow, "backend_application") + + def test_create_resets_oauth2_flow_for_non_oauth2_auth_type(self): + ws = self.env["webservice.backend"].create( + { + "name": "WebService No Auth", + "tech_name": "test_oauth2_reset_create", + "auth_type": "none", + "protocol": "http", + "url": self.url, + # Inconsistent on purpose: no `create`/`write` should ever + # leave this set together with a non-oauth2 `auth_type`. + "oauth2_flow": "backend_application", + } + ) + self.assertFalse(ws.oauth2_flow) + + def test_onchange_resets_oauth2_flow(self): + ws = self.webservice.new( + {"auth_type": "oauth2", "oauth2_flow": "backend_application"} + ) + ws.auth_type = "none" + ws._onchange_auth_type() + self.assertFalse(ws.oauth2_flow) diff --git a/webservice_server_env/__init__.py b/webservice_server_env/__init__.py index 071962a3..1a9a001c 100644 --- a/webservice_server_env/__init__.py +++ b/webservice_server_env/__init__.py @@ -1,2 +1,2 @@ from . import models -from .hooks import uninstall_hook +from .hooks import post_init_hook, uninstall_hook diff --git a/webservice_server_env/__manifest__.py b/webservice_server_env/__manifest__.py index ae315cb6..db9a1c10 100644 --- a/webservice_server_env/__manifest__.py +++ b/webservice_server_env/__manifest__.py @@ -14,6 +14,7 @@ "author": "Creu Blanca, Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/web-api", "depends": ["web", "webservice", "server_environment"], + "post_init_hook": "post_init_hook", "uninstall_hook": "uninstall_hook", "auto_install": True, } diff --git a/webservice_server_env/hooks.py b/webservice_server_env/hooks.py index 4eeb39d5..b860e489 100644 --- a/webservice_server_env/hooks.py +++ b/webservice_server_env/hooks.py @@ -3,32 +3,37 @@ from odoo.addons.server_environment.uninstall import restore_env_managed_columns +ENV_MANAGED_FIELDS = [ + "protocol", + "url", + "auth_type", + "username", + "password", + "api_key", + "api_key_header", + "content_type", + "oauth2_flow", + "oauth2_scope", + "oauth2_clientid", + "oauth2_client_secret", + "oauth2_authorization_url", + "oauth2_token_url", + "oauth2_audience", + "oauth2_token_method", + "oauth2_client_auth_method", + "oauth2_client_auth_header", + "oauth2_client_auth_value", +] -def uninstall_hook(env): - """Restore database columns dropped by server.env.mixin. - When the module is uninstalled, the columns managed by the server - environment mixin must be restored and repopulated with current values, - so the database remains usable. - """ +def post_init_hook(env): + env["webservice.backend"]._preserve_not_env_managed_data(ENV_MANAGED_FIELDS) + + +def uninstall_hook(env): + """Restore database columns dropped by server.env.mixin.""" restore_env_managed_columns( env, "webservice.backend", - [ - "protocol", - "url", - "auth_type", - "username", - "password", - "api_key", - "api_key_header", - "content_type", - "oauth2_flow", - "oauth2_scope", - "oauth2_clientid", - "oauth2_client_secret", - "oauth2_authorization_url", - "oauth2_token_url", - "oauth2_audience", - ], + ENV_MANAGED_FIELDS, )