Skip to content

base_remote: API authentication broken due to missing HTTP context check #3458

@dtgagnon

Description

@dtgagnon

API/RPC Authentication Fails with "Expected singleton: res.remote()"

Module

base_remote

Describe the bug

The base_remote module breaks XML-RPC and JSON-RPC authentication, preventing external API clients (OdooRPC Python library, mobile apps using direct RPC, automated scripts, etc.) from authenticating to Odoo. Authentication fails with ValueError: Expected singleton: res.remote() while web browser authentication continues to work normally.

To Reproduce

Affected versions: 18.0.1.0.0 (likely affects earlier versions as well)

Steps to reproduce the behavior:

  1. Install the base_remote module
  2. Attempt to authenticate via XML-RPC or JSON-RPC from an external client:
    import xmlrpc.client
    
    url = 'http://your-odoo-server:8069'
    common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
    uid = common.authenticate('database', 'username', 'password', {})
  3. Authentication fails with error: ValueError: Expected singleton: res.remote()

Expected behavior
API/RPC authentication should succeed, just as web browser authentication does. External API clients should be able to authenticate and interact with Odoo programmatically.

Additional context

Root cause:
In models/base.py, the remote property gracefully handles missing HTTP context (which occurs during RPC calls) by returning an empty recordset:

@property
def remote(self):
    try:
        remote_addr = http.request.httprequest.remote_addr
    except (KeyError, AttributeError, RuntimeError):
        return self.env["res.remote"]  # Returns empty recordset for RPC calls

However, models/res_users.py unconditionally calls ensure_one() on this recordset:

@classmethod
def _auth_check_remote(cls, credential, method):
    with cls.pool.cursor() as cr:
        env = api.Environment(cr, SUPERUSER_ID, {})
        remote = env["res.users"].remote
        if not config["test_enable"]:
            remote.ensure_one()  # Crashes when remote is empty recordset
  • Web browser logins: Have HTTP context → remote returns 1 record → ensure_one() succeeds ✓
  • API/RPC logins: No HTTP context → remote returns empty recordset → ensure_one() fails ❌

Proposed fix:
Change line 19 in models/res_users.py:

# Current:
if not config["test_enable"]:
    remote.ensure_one()

# Fixed:
if not config["test_enable"] and remote:
    remote.ensure_one()

This allows the singleton check to be skipped when no HTTP context exists (RPC calls), while still enforcing device tracking for web browser connections (the module's primary purpose). This follows the same pattern already used for test mode and doesn't compromise the module's functionality.

Environment:

  • Odoo 18.0
  • Python 3.12
  • Tested with OdooRPC library and direct XML-RPC calls

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions