From da8a0363b8c24a5f5043bdb821d219226bbf9078 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 27 Aug 2026 16:24:36 +0200 Subject: [PATCH 1/2] [ADD] website_core: split core out of webservice webservice_core: improve tech_name * go to mixin to be reusable * auto-generate it and auto-clean it --- requirements.txt | 2 + webservice/README.rst | 5 + webservice/__manifest__.py | 6 +- webservice/components/request_adapter.py | 3 +- webservice/models/webservice_backend.py | 70 +-- webservice/readme/DESCRIPTION.md | 4 + webservice/static/description/index.html | 4 + webservice/tests/__init__.py | 2 - webservice/views/webservice_backend.xml | 181 ++----- webservice_core/README.rst | 102 ++++ webservice_core/__init__.py | 2 + webservice_core/__manifest__.py | 22 + webservice_core/hooks.py | 26 ++ webservice_core/models/__init__.py | 2 + webservice_core/models/webservice_backend.py | 19 + .../models/webservice_request_mixin.py | 237 ++++++++++ webservice_core/pyproject.toml | 3 + webservice_core/readme/CONFIGURE.md | 22 + webservice_core/readme/CONTRIBUTORS.md | 3 + webservice_core/readme/DESCRIPTION.md | 7 + webservice_core/readme/USAGE.md | 58 +++ .../security/ir.model.access.csv | 0 .../security/ir_rule.xml | 0 webservice_core/static/description/icon.png | Bin 0 -> 9455 bytes webservice_core/static/description/index.html | 442 ++++++++++++++++++ webservice_core/tests/__init__.py | 2 + webservice_core/tests/common.py | 38 ++ .../tests/test_utils.py | 2 +- .../tests/test_webservice.py | 94 ++++ {webservice => webservice_core}/utils.py | 0 webservice_core/views/webservice_backend.xml | 103 ++++ 31 files changed, 1257 insertions(+), 204 deletions(-) create mode 100644 webservice_core/README.rst create mode 100644 webservice_core/__init__.py create mode 100644 webservice_core/__manifest__.py create mode 100644 webservice_core/hooks.py create mode 100644 webservice_core/models/__init__.py create mode 100644 webservice_core/models/webservice_backend.py create mode 100644 webservice_core/models/webservice_request_mixin.py create mode 100644 webservice_core/pyproject.toml create mode 100644 webservice_core/readme/CONFIGURE.md create mode 100644 webservice_core/readme/CONTRIBUTORS.md create mode 100644 webservice_core/readme/DESCRIPTION.md create mode 100644 webservice_core/readme/USAGE.md rename {webservice => webservice_core}/security/ir.model.access.csv (100%) rename {webservice => webservice_core}/security/ir_rule.xml (100%) create mode 100644 webservice_core/static/description/icon.png create mode 100644 webservice_core/static/description/index.html create mode 100644 webservice_core/tests/__init__.py create mode 100644 webservice_core/tests/common.py rename {webservice => webservice_core}/tests/test_utils.py (85%) rename {webservice => webservice_core}/tests/test_webservice.py (69%) rename {webservice => webservice_core}/utils.py (100%) create mode 100644 webservice_core/views/webservice_backend.xml diff --git a/requirements.txt b/requirements.txt index 45e2e80f..2cdbab3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ # generated from manifests external_dependencies oauthlib +openupgradelib +requests requests-oauthlib responses diff --git a/webservice/README.rst b/webservice/README.rst index 57519604..6cf262a5 100644 --- a/webservice/README.rst +++ b/webservice/README.rst @@ -38,6 +38,11 @@ The module introduces support for HTTP Request protocol. The webservice HTTP call returns by default the content of the response. A context 'content_only' can be passed to get the full response object. +It builds on top of ``webservice_core`` (which provides the +``webservice.backend`` model with public/username-password/API key +authentication) to add OAuth2 authentication and ``server_environment`` +support. + **Table of contents** .. contents:: diff --git a/webservice/__manifest__.py b/webservice/__manifest__.py index 6c77df48..775ad3e9 100644 --- a/webservice/__manifest__.py +++ b/webservice/__manifest__.py @@ -9,14 +9,12 @@ "version": "18.0.1.1.2", "license": "AGPL-3", "development_status": "Production/Stable", - "maintainers": ["etobella"], + "maintainers": ["etobella", "simahawk"], "author": "Creu Blanca, Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/web-api", - "depends": ["component"], + "depends": ["webservice_core", "component"], "external_dependencies": {"python": ["requests-oauthlib", "oauthlib", "responses"]}, "data": [ - "security/ir.model.access.csv", - "security/ir_rule.xml", "views/webservice_backend.xml", ], "demo": [], diff --git a/webservice/components/request_adapter.py b/webservice/components/request_adapter.py index a7f7c943..4b5519b8 100644 --- a/webservice/components/request_adapter.py +++ b/webservice/components/request_adapter.py @@ -12,8 +12,7 @@ from requests_oauthlib import OAuth2Session from odoo.addons.component.core import Component - -from ..utils import sanitize_url_for_log +from odoo.addons.webservice_core.utils import sanitize_url_for_log _logger = logging.getLogger(__name__) diff --git a/webservice/models/webservice_backend.py b/webservice/models/webservice_backend.py index 3f463799..22b9c8d3 100644 --- a/webservice/models/webservice_backend.py +++ b/webservice/models/webservice_backend.py @@ -5,7 +5,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from odoo import _, api, exceptions, fields, models +from odoo import api, fields, models from odoo.tools import config _logger = logging.getLogger(__name__) @@ -13,26 +13,15 @@ class WebserviceBackend(models.Model): _name = "webservice.backend" - _inherit = ["collection.base"] - _description = "WebService Backend" + _inherit = [ + "webservice.backend", + "collection.base", + ] - name = fields.Char(required=True) - tech_name = fields.Char(required=True) - protocol = fields.Selection([("http", "HTTP Request")], required=True) - url = fields.Char(required=True) auth_type = fields.Selection( - selection=[ - ("none", "Public"), - ("user_pwd", "Username & password"), - ("api_key", "API Key"), - ("oauth2", "OAuth2"), - ], - required=True, + selection_add=[("oauth2", "OAuth2")], + ondelete={"oauth2": "cascade"}, ) - username = fields.Char(auth_type="user_pwd") - password = fields.Char(auth_type="user_pwd") - api_key = fields.Char(string="API Key", auth_type="api_key") - api_key_header = fields.Char(string="API Key header", auth_type="api_key") oauth2_flow = fields.Selection( [ ("backend_application", "Backend Application (Client Credentials Grant)"), @@ -58,47 +47,6 @@ class WebserviceBackend(models.Model): help="random key generated when authorization flow starts " "to ensure that no CSRF attack happen" ) - content_type = fields.Selection( - [ - ("application/json", "JSON"), - ("application/xml", "XML"), - ("application/x-www-form-urlencoded", "Form"), - ], - ) - company_id = fields.Many2one("res.company", string="Company") - - @api.constrains("auth_type") - def _check_auth_type(self): - valid_fields = { - k: v for k, v in self._fields.items() if hasattr(v, "auth_type") - } - for rec in self: - if rec.auth_type == "none": - continue - _fields = [v for v in valid_fields.values() if v.auth_type == rec.auth_type] - missing = [] - for _field in _fields: - if not rec[_field.name]: - missing.append(_field) - if missing: - raise exceptions.UserError(rec._msg_missing_auth_param(missing)) - - def _msg_missing_auth_param(self, missing_fields): - def get_selection_value(fname): - return self._fields.get(fname).convert_to_export(self[fname], self) - - return _( - "Webservice '%(name)s' requires '%(auth_type)s' authentication. " - "However, the following field(s) are not valued: %(fields)s" - ) % { - "name": self.name, - "auth_type": get_selection_value("auth_type"), - "fields": ", ".join([f.string for f in missing_fields]), - } - - 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): @@ -125,6 +73,10 @@ def write(self, vals): return res def call(self, method, *args, **kwargs): + if not self.auth_type.startswith("oauth2"): + return super().call(method, *args, **kwargs) + # NOTE: oauth2 still relies on `component` for now, until it gets + # extracted to its own module and reworked to drop that dependency too. _logger.debug("backend %s: call %s %s %s", self.name, method, args, kwargs) response = getattr(self._get_adapter(), method)(*args, **kwargs) _logger.debug("backend %s: response: \n%s", self.name, response) diff --git a/webservice/readme/DESCRIPTION.md b/webservice/readme/DESCRIPTION.md index cdd44bd7..1acbda1f 100644 --- a/webservice/readme/DESCRIPTION.md +++ b/webservice/readme/DESCRIPTION.md @@ -1,3 +1,7 @@ This module creates WebService frameworks to be used globally. The module introduces support for HTTP Request protocol. The webservice HTTP call returns by default the content of the response. A context 'content_only' can be passed to get the full response object. + +It builds on top of ``webservice_core`` (which provides the ``webservice.backend`` +model with public/username-password/API key authentication) to add OAuth2 +authentication and ``server_environment`` support. diff --git a/webservice/static/description/index.html b/webservice/static/description/index.html index 1955b42d..bfd54736 100644 --- a/webservice/static/description/index.html +++ b/webservice/static/description/index.html @@ -379,6 +379,10 @@

WebService

The module introduces support for HTTP Request protocol. The webservice HTTP call returns by default the content of the response. A context ‘content_only’ can be passed to get the full response object.

+

It builds on top of webservice_core (which provides the +webservice.backend model with public/username-password/API key +authentication) to add OAuth2 authentication and server_environment +support.

Table of contents