Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion webservice/tests/test_oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ def test_oauth2_flow_compute_with_ui(self):
form_xmlid = "webservice.webservice_backend_form_view"
for auth_type, oauth2_flow in [
(tp, fl)
for tp in ws._fields["auth_type"].get_values(ws.env)
# Only test auth types from ``webservice`` and its dependencies: other
# modules may add values requiring extra fields this test doesn't fill
for tp in ["none", "user_pwd", "api_key", "oauth2"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a small comment to explain why these values are hardcoded?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankfully I wrote it in the commit message, because I didn't remember why I hardcoded the values 😅

The test `test_oauth2_flow_compute_with_ui` iterates over all available `auth_type` values.

When a module extends `webservice` with an additional `auth_type` (e.g., `client_certificate`),
this test fails because it does not know how to populate the new required fields for that type.

This commit restricts the test loop to the authentication types defined in `webservice` module.

Comment is added

for fl in ws._fields["oauth2_flow"].get_values(ws.env)
]:
next_ws_id = ws.sudo().search([], order="id desc", limit=1).id + 1
Expand Down
110 changes: 110 additions & 0 deletions webservice_client_certificate_auth/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
==============================================
WebService - Client Certificate Authentication
==============================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4a2f96ce5a7f3c69babb934fd5a9e2bb530fd01e335c3526d5f499774b59fd36
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |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%2Fweb--api-lightgray.png?logo=github
:target: https://github.com/OCA/web-api/tree/18.0/webservice_client_certificate_auth
:alt: OCA/web-api
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-api-18-0/web-api-18-0-webservice_client_certificate_auth
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web-api&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Adds support for Client Side Certificates to the ``webservice_core``
module.

**Table of contents**

.. contents::
:local:

Configuration
=============

On the webservice backend, set the authentication type to "Client
Certificate" and fill in:

- **Client Certificate Path:** path to the certificate file (or to a
single file containing both the certificate and the private key).
- **Client Private Key Path:** optional, leave empty if the private key
is bundled in the certificate file.

To manage these paths with ``server_environment``, see
``webservice_client_certificate_auth_server_env``.

Usage
=====

When a call is made using the backend, the ``cert`` parameter is
automatically injected into the underlying Python ``requests`` call
based on the provided configuration:

- **Certificate only:** Passed as ``cert='/path/to/file'`` (a single
file containing the private key and the certificate).
- **Certificate and Key:** Passed as a tuple
``cert=('/path/to/crt', '/path/to/key')``.

Warning: the private key to your local certificate must be unencrypted.
Currently, ``requests`` does not support using encrypted keys.

See `Requests: Client Side
Certificates <https://requests.readthedocs.io/en/latest/user/advanced/#client-side-certificates>`__
for underlying implementation details.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/web-api/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
`feedback <https://github.com/OCA/web-api/issues/new?body=module:%20webservice_client_certificate_auth%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
-------

* Camptocamp

Contributors
------------

- Vincent Van Rossem <vincent.vanrossem@camptocamp.com>

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/web-api <https://github.com/OCA/web-api/tree/18.0/webservice_client_certificate_auth>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions webservice_client_certificate_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
14 changes: 14 additions & 0 deletions webservice_client_certificate_auth/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2026 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "WebService - Client Certificate Authentication",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web-api",
"depends": ["webservice_core"],
"data": [
"views/webservice_backend.xml",
],
}
1 change: 1 addition & 0 deletions webservice_client_certificate_auth/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import webservice_backend
39 changes: 39 additions & 0 deletions webservice_client_certificate_auth/models/webservice_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2026 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class WebserviceBackend(models.Model):
_inherit = "webservice.backend"

auth_type = fields.Selection(
selection_add=[("client_certificate", "Client Certificate")],
ondelete={"client_certificate": lambda recs: recs.write({"auth_type": "none"})},
)
client_certificate_path = fields.Char(
auth_type="client_certificate",
)
client_private_key_path = fields.Char()

def _request(self, method, url=None, url_params=None, **kwargs):
if self.auth_type == "client_certificate":
# ``requests`` ``cert`` parameter accepts:
# * A string: path to a file containing both certificate and private key
# * A tuple: ('/path/client.cert', '/path/client.key')
cert_path = self._get_cert_path()
key_path = self._get_key_path()

if "cert" not in kwargs and cert_path:
if key_path:
kwargs["cert"] = (cert_path, key_path)
else:
kwargs["cert"] = cert_path

return super()._request(method, url=url, url_params=url_params, **kwargs)

def _get_cert_path(self):
return self.client_certificate_path

def _get_key_path(self):
return self.client_private_key_path
3 changes: 3 additions & 0 deletions webservice_client_certificate_auth/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
6 changes: 6 additions & 0 deletions webservice_client_certificate_auth/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
On the webservice backend, set the authentication type to "Client Certificate" and fill in:

* **Client Certificate Path:** path to the certificate file (or to a single file containing both the certificate and the private key).
* **Client Private Key Path:** optional, leave empty if the private key is bundled in the certificate file.

To manage these paths with `server_environment`, see `webservice_client_certificate_auth_server_env`.
1 change: 1 addition & 0 deletions webservice_client_certificate_auth/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Vincent Van Rossem \<<vincent.vanrossem@camptocamp.com>\>
1 change: 1 addition & 0 deletions webservice_client_certificate_auth/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds support for Client Side Certificates to the `webservice_core` module.
9 changes: 9 additions & 0 deletions webservice_client_certificate_auth/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
When a call is made using the backend, the `cert` parameter is automatically injected into the underlying Python `requests`
call based on the provided configuration:

* **Certificate only:** Passed as `cert='/path/to/file'` (a single file containing the private key and the certificate).
* **Certificate and Key:** Passed as a tuple `cert=('/path/to/crt', '/path/to/key')`.

Warning: the private key to your local certificate must be unencrypted. Currently, `requests` does not support using encrypted keys.

See [Requests: Client Side Certificates](https://requests.readthedocs.io/en/latest/user/advanced/#client-side-certificates) for underlying implementation details.
Loading
Loading