details: https://code.tryton.org/tryton/commit/0e84ed4f4db8
branch: default
user: Cédric Krier <[email protected]>
date: Wed Jan 21 14:55:49 2026 +0100
description:
Add support for basic authentication for user application
Closes #14528
diffstat:
trytond/CHANGELOG | 1 +
trytond/doc/topics/user_application.rst | 5 +-
trytond/trytond/protocols/wrappers.py | 12 +++-
trytond/trytond/tests/test_protocols.py | 86 ++++++++++++++++++++++++++++++++-
4 files changed, 97 insertions(+), 7 deletions(-)
diffs (153 lines):
diff -r 7fd1ad0f0509 -r 0e84ed4f4db8 trytond/CHANGELOG
--- a/trytond/CHANGELOG Thu Jan 22 18:32:26 2026 +0100
+++ b/trytond/CHANGELOG Wed Jan 21 14:55:49 2026 +0100
@@ -1,3 +1,4 @@
+* Add support for basic authentication for user application
* Support the conversion of MJML reports to HTML
* Allow filtering users to be notified by cron tasks
diff -r 7fd1ad0f0509 -r 0e84ed4f4db8 trytond/doc/topics/user_application.rst
--- a/trytond/doc/topics/user_application.rst Thu Jan 22 18:32:26 2026 +0100
+++ b/trytond/doc/topics/user_application.rst Wed Jan 21 14:55:49 2026 +0100
@@ -52,8 +52,9 @@
``user_application(name[, json])``
Set the :attr:`~trytond.transaction.Transaction.user` from the
- ``Authorization`` header using the type ``bearer`` and a valid key for the
- named user application.
+ ``Authorization`` header using the ``bearer`` type with the user application
+ key, or the ``basic`` type without a username and with the user application
+ key as the password.
User Application Key
====================
diff -r 7fd1ad0f0509 -r 0e84ed4f4db8 trytond/trytond/protocols/wrappers.py
--- a/trytond/trytond/protocols/wrappers.py Thu Jan 22 18:32:26 2026 +0100
+++ b/trytond/trytond/protocols/wrappers.py Wed Jan 21 14:55:49 2026 +0100
@@ -292,13 +292,17 @@
authorization = parse_authorization_header(header)
if authorization is None:
abort(HTTPStatus.UNAUTHORIZED)
- if authorization.type != 'bearer':
- abort(HTTPStatus.FORBIDDEN)
- token = getattr(authorization, 'token', '')
+ if authorization.type == 'bearer':
+ token = getattr(authorization, 'token', '')
+ elif authorization.type == 'basic':
+ token = authorization.get('password')
+ else:
+ abort(HTTPStatus.UNAUTHORIZED)
+
application = UserApplication.check(token, name)
if not application:
- abort(HTTPStatus.FORBIDDEN)
+ abort(HTTPStatus.UNAUTHORIZED)
transaction = Transaction()
# TODO language
with transaction.set_user(application.user.id), \
diff -r 7fd1ad0f0509 -r 0e84ed4f4db8 trytond/trytond/tests/test_protocols.py
--- a/trytond/trytond/tests/test_protocols.py Thu Jan 22 18:32:26 2026 +0100
+++ b/trytond/trytond/tests/test_protocols.py Wed Jan 21 14:55:49 2026 +0100
@@ -3,12 +3,17 @@
import datetime
import json
+from base64 import b64encode
from decimal import Decimal
+from trytond.pool import Pool
from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder, JSONRequest
+from trytond.protocols.wrappers import (
+ HTTPStatus, Response, user_application, with_pool, with_transaction)
from trytond.protocols.xmlrpc import XMLRequest, client
-from trytond.tests.test_tryton import TestCase
+from trytond.tests.test_tryton import Client, RouteTestCase, TestCase
from trytond.tools.immutabledict import ImmutableDict
+from trytond.wsgi import TrytondWSGI
def _identity(x):
@@ -130,3 +135,82 @@
result, _ = client.loads(s)
result, = result
self.assertEqual(result, Decimal('3.141592653589793'))
+
+
+class UserApplication(RouteTestCase):
+ module = 'res'
+
+ @classmethod
+ def setUpDatabase(cls):
+ pool = Pool()
+ User = pool.get('res.user')
+ UserApplication = pool.get('res.user.application')
+
+ UserApplication.application.selection.append(('test', "Test"))
+
+ User.create([{
+ 'login': 'user',
+ 'applications': [('create', [{
+ 'key': 'secret_key',
+ 'application': 'test',
+ 'state': 'validated',
+ }])],
+ }])
+
+ def test_authorization_bearer(self):
+ app = TrytondWSGI()
+ test_application = user_application('test')
+
+ @app.route('/<database_name>/test/user_application')
+ @with_pool
+ @with_transaction()
+ @test_application
+ def _route(request, pool):
+ return ''
+
+ client = Client(app, Response)
+ response = client.get(
+ f'/{self.db_name}/test/user_application',
+ headers=[
+ ('Authorization', 'bearer secret_key'),
+ ])
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+
+ def test_authorization_basic(self):
+ app = TrytondWSGI()
+ test_application = user_application('test')
+
+ @app.route('/<database_name>/test/user_application')
+ @with_pool
+ @with_transaction()
+ @test_application
+ def _route(request, pool):
+ return ''
+
+ client = Client(app, Response)
+ auth = b64encode(b':secret_key').decode('ascii')
+ response = client.get(
+ f'/{self.db_name}/test/user_application',
+ headers=[
+ ('Authorization', f'basic {auth}'),
+ ])
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+
+ def test_authorization_bad_auth(self):
+ app = TrytondWSGI()
+ test_application = user_application('test')
+
+ @app.route('/<database_name>/test/user_application')
+ @with_pool
+ @with_transaction()
+ @test_application
+ def _route(request, pool):
+ return ''
+
+ client = Client(app, Response)
+ response = client.get(
+ f'/{self.db_name}/test/user_application',
+ headers=[
+ ('Authorization', 'bearer foo'),
+ ])
+ self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED)