From 647787dbe54927c51f7bc83591ec2755de6befba Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Mon, 4 Mar 2024 18:20:43 +0100 Subject: [PATCH] [FIX] endpoint_route_handler: Use dedicated cursor for registry When the request cursor is used to instantiate the EndpointRegistry in the call to routing_map, the READ REPEATABLE isolation level will ensure that any value read from the DB afterwards, will be the same than when the first SELECT is executed. This is breaking the oauth flow as the oauth token that is written at the beggining of the oauth process cannot be read by the cursor computing the session token, which will read an old value. Therefore when the session security check is performed, the session token is outdated as the new session token is computed using an up to date cursor. By using a dedicated cursor to instantiate the EndpointRegistry, we ensure no read is performed on the database using the request cursor which will in turn use the updated value of the oauth token to compute the session token, and the security check will not fail. --- endpoint_route_handler/models/ir_http.py | 51 +++++++++++++++++------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/endpoint_route_handler/models/ir_http.py b/endpoint_route_handler/models/ir_http.py index e684f0d2..0dee55cc 100644 --- a/endpoint_route_handler/models/ir_http.py +++ b/endpoint_route_handler/models/ir_http.py @@ -7,7 +7,7 @@ import werkzeug -from odoo import http, models +from odoo import http, models, registry as registry_get from ..registry import EndpointRegistry @@ -18,8 +18,8 @@ class IrHttp(models.AbstractModel): _inherit = "ir.http" @classmethod - def _endpoint_route_registry(cls, env): - return EndpointRegistry.registry_for(env.cr) + def _endpoint_route_registry(cls, cr): + return EndpointRegistry.registry_for(cr) @classmethod def _generate_routing_rules(cls, modules, converters): @@ -32,7 +32,7 @@ def _generate_routing_rules(cls, modules, converters): @classmethod def _endpoint_routing_rules(cls): """Yield custom endpoint rules""" - e_registry = cls._endpoint_route_registry(http.request.env) + e_registry = cls._endpoint_route_registry(http.request.env.cr) for endpoint_rule in e_registry.get_rules(): _logger.debug("LOADING %s", endpoint_rule) endpoint = endpoint_rule.endpoint @@ -41,20 +41,41 @@ def _endpoint_routing_rules(cls): @classmethod def routing_map(cls, key=None): - last_version = cls._get_routing_map_last_version(http.request.env) - if not hasattr(cls, "_routing_map"): - # routing map just initialized, store last update for this env - cls._endpoint_route_last_version = last_version - elif cls._endpoint_route_last_version < last_version: - _logger.info("Endpoint registry updated, reset routing map") - cls._routing_map = {} - cls._rewrite_len = {} - cls._endpoint_route_last_version = last_version + # When the request cursor is used to instantiate the EndpointRegistry + # in the call to routing_map, the READ REPEATABLE isolation level + # will ensure that any value read from the DB afterwards, will be the + # same than when the first SELECT is executed. + # + # This is breaking the oauth flow as the oauth token that is written + # at the beggining of the oauth process cannot be read by the cursor + # computing the session token, which will read an old value. Therefore + # when the session security check is performed, the session token + # is outdated as the new session token is computed using an up to date + # cursor. + # + # By using a dedicated cursor to instantiate the EndpointRegistry, we + # ensure no read is performed on the database using the request cursor + # which will in turn use the updated value of the oauth token to compute + # the session token, and the security check will not fail. + registry = registry_get(http.request.env.cr.dbname) + with registry.cursor() as cr: + last_version = cls._get_routing_map_last_version(cr) + if not hasattr(cls, "_routing_map"): + _logger.debug( + "routing map just initialized, store last update for this env" + ) + # routing map just initialized, store last update for this env + cls._endpoint_route_last_version = last_version + elif cls._endpoint_route_last_version < last_version: + _logger.info("Endpoint registry updated, reset routing map") + cls._routing_map = {} + cls._rewrite_len = {} + cls._endpoint_route_last_version = last_version return super().routing_map(key=key) @classmethod - def _get_routing_map_last_version(cls, env): - return cls._endpoint_route_registry(env).last_version() + def _get_routing_map_last_version(cls, cr): + return cls._endpoint_route_registry(cr).last_version() @classmethod def _clear_routing_map(cls):