mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-18 06:12:01 +02:00
add rpc addon and fix 19.0 compatibility issues
- add missing rpc addon (auto_install, required by server_wide_modules)
- add __init__.py -> init.py symlink for odoo package importability
- re-export image_process from odoo.tools (needed by web_editor)
- add backward-compatible slug/unslug functions in http_routing
🤖 assisted by claude
This commit is contained in:
parent
2d3ee4855a
commit
3037cab43e
11 changed files with 629 additions and 0 deletions
1
odoo-bringout-oca-ocb-base/odoo/__init__.py
Symbolic link
1
odoo-bringout-oca-ocb-base/odoo/__init__.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
init.py
|
||||
|
|
@ -18,3 +18,4 @@ from .translate import _, html_translate, xml_translate, LazyTranslate
|
|||
from .xml_utils import cleanup_xml_node, load_xsd_files_from_url, validate_xml_from_attachment
|
||||
from .convert import convert_csv_import, convert_file, convert_sql_import, convert_xml_import
|
||||
from .set_expression import SetDefinitions
|
||||
from .image import image_process, image_data_uri, base64_to_image, image_to_base64
|
||||
|
|
|
|||
|
|
@ -628,3 +628,14 @@ class IrHttp(models.AbstractModel):
|
|||
except werkzeug.exceptions.NotFound:
|
||||
new_url = path
|
||||
return new_url or path, endpoint and endpoint[0]
|
||||
|
||||
|
||||
# Backward-compatible module-level functions that delegate to IrHttp class methods.
|
||||
# In Odoo 19.0 these were moved to IrHttp._slug/_unslug, but many addons
|
||||
# still import them as standalone functions.
|
||||
def slug(value):
|
||||
return IrHttp._slug(value)
|
||||
|
||||
|
||||
def unslug(value):
|
||||
return IrHttp._unslug(value)
|
||||
|
|
|
|||
1
odoo-bringout-oca-ocb-rpc/rpc/__init__.py
Normal file
1
odoo-bringout-oca-ocb-rpc/rpc/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import controllers
|
||||
15
odoo-bringout-oca-ocb-rpc/rpc/__manifest__.py
Normal file
15
odoo-bringout-oca-ocb-rpc/rpc/__manifest__.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
'name': 'RPC endpoints',
|
||||
'description': """\
|
||||
Standard Odoo RPC endpoints to models
|
||||
=====================================
|
||||
|
||||
This module provides the /xmlrpc and /jsonrpc endpoints used to
|
||||
programmatically access models.
|
||||
""",
|
||||
'depends': ["base"],
|
||||
'category': 'Extra Tools',
|
||||
'auto_install': True,
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
29
odoo-bringout-oca-ocb-rpc/rpc/controllers/__init__.py
Normal file
29
odoo-bringout-oca-ocb-rpc/rpc/controllers/__init__.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import odoo.release
|
||||
from odoo.http import request, route
|
||||
|
||||
from . import json2
|
||||
|
||||
RPC_DEPRECATION_NOTICE = """\
|
||||
The /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints are deprecated in Odoo 19 \
|
||||
and scheduled for removal in Odoo 20. Please report the problem to the \
|
||||
client making the request.
|
||||
Mute this logger: --log-handler %s:ERROR
|
||||
https://www.odoo.com/documentation/latest/developer/reference/external_api.html#migrating-from-xml-rpc-json-rpc"""
|
||||
|
||||
|
||||
def _check_request():
|
||||
if request.db:
|
||||
request.env.cr.close()
|
||||
|
||||
|
||||
from .jsonrpc import JSONRPC # noqa: E402
|
||||
from .xmlrpc import XMLRPC # noqa: E402
|
||||
|
||||
|
||||
class RPC(XMLRPC, JSONRPC):
|
||||
@route(['/web/version', '/json/version'], type='http', auth='none', readonly=True)
|
||||
def version(self):
|
||||
return request.make_json_response({
|
||||
'version_info': odoo.release.version_info,
|
||||
'version': odoo.release.version,
|
||||
})
|
||||
90
odoo-bringout-oca-ocb-rpc/rpc/controllers/json2.py
Normal file
90
odoo-bringout-oca-ocb-rpc/rpc/controllers/json2.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from werkzeug.exceptions import (
|
||||
NotFound,
|
||||
UnprocessableEntity,
|
||||
)
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.models import BaseModel
|
||||
from odoo.service.model import get_public_method
|
||||
from odoo.tools import frozendict
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebJson2Controller(http.Controller):
|
||||
def _web_json_2_rpc_readonly(self, rule, args):
|
||||
try:
|
||||
model_name = args['__model__']
|
||||
method_name = args['__method__']
|
||||
Model = request.registry[model_name]
|
||||
except KeyError:
|
||||
# no need of a read/write cursor to send a 404 http error
|
||||
return True
|
||||
for cls in Model.mro():
|
||||
method = getattr(cls, method_name, None)
|
||||
if method is not None and hasattr(method, '_readonly'):
|
||||
return method._readonly
|
||||
return False
|
||||
|
||||
# Take over /json/<path:subpath>
|
||||
@http.route(
|
||||
['/json/2', '/json/2/<path:subpath>'],
|
||||
auth='public',
|
||||
type='json2',
|
||||
readonly=True,
|
||||
methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||
)
|
||||
def web_json_2_404(self, subpath=None):
|
||||
e = "Did you mean POST /json/2/<model>/<method>?"
|
||||
raise request.not_found(e)
|
||||
|
||||
@http.route(
|
||||
'/json/2/<__model__>/<__method__>',
|
||||
methods=['POST'],
|
||||
auth='bearer',
|
||||
type='json2',
|
||||
readonly=_web_json_2_rpc_readonly,
|
||||
save_session=False,
|
||||
)
|
||||
def web_json_2_rpc(
|
||||
self,
|
||||
__model__: str,
|
||||
__method__: str,
|
||||
ids: Sequence[int] = (),
|
||||
context: Mapping[str, Any] = frozendict(),
|
||||
**kwargs,
|
||||
):
|
||||
try:
|
||||
Model = request.env[__model__].with_context(context)
|
||||
except KeyError as exc:
|
||||
e = f"the model {__model__!r} does not exist"
|
||||
raise NotFound(e) from exc
|
||||
|
||||
try:
|
||||
func = get_public_method(Model, __method__)
|
||||
except AttributeError as exc:
|
||||
raise NotFound(exc.args[0]) from exc
|
||||
if hasattr(func, '_api_model') and ids:
|
||||
e = f"cannot call {__model__}.{__method__} with ids"
|
||||
raise UnprocessableEntity(e)
|
||||
|
||||
records = Model.browse(ids)
|
||||
signature = inspect.signature(func)
|
||||
try:
|
||||
signature.bind(records, **kwargs)
|
||||
except TypeError as exc:
|
||||
raise UnprocessableEntity(exc.args[0])
|
||||
|
||||
result = func(records, **kwargs)
|
||||
if isinstance(result, BaseModel):
|
||||
result = result.ids
|
||||
|
||||
return result
|
||||
16
odoo-bringout-oca-ocb-rpc/rpc/controllers/jsonrpc.py
Normal file
16
odoo-bringout-oca-ocb-rpc/rpc/controllers/jsonrpc.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import logging
|
||||
|
||||
from odoo.http import Controller, dispatch_rpc, route
|
||||
|
||||
from . import RPC_DEPRECATION_NOTICE, _check_request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JSONRPC(Controller):
|
||||
@route('/jsonrpc', type='jsonrpc', auth="none", save_session=False)
|
||||
def jsonrpc(self, service, method, args):
|
||||
""" Method used by client APIs to contact OpenERP. """
|
||||
logger.warning(RPC_DEPRECATION_NOTICE, __name__)
|
||||
_check_request()
|
||||
return dispatch_rpc(service, method, args)
|
||||
169
odoo-bringout-oca-ocb-rpc/rpc/controllers/xmlrpc.py
Normal file
169
odoo-bringout-oca-ocb-rpc/rpc/controllers/xmlrpc.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
import xmlrpc.client
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
import odoo.exceptions
|
||||
from odoo.fields import Command, Date, Datetime
|
||||
from odoo.http import Controller, Response, dispatch_rpc, request, route
|
||||
from odoo.tools import lazy
|
||||
from odoo.tools.misc import frozendict
|
||||
|
||||
from . import RPC_DEPRECATION_NOTICE, _check_request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# XML-RPC fault codes. Some care must be taken when changing these: the
|
||||
# constants are also defined client-side and must remain in sync.
|
||||
# User code must use the exceptions defined in ``odoo.exceptions`` (not
|
||||
# create directly ``xmlrpc.client.Fault`` objects).
|
||||
RPC_FAULT_CODE_CLIENT_ERROR = 1 # indistinguishable from app. error.
|
||||
RPC_FAULT_CODE_APPLICATION_ERROR = 1
|
||||
RPC_FAULT_CODE_WARNING = 2
|
||||
RPC_FAULT_CODE_ACCESS_DENIED = 3
|
||||
RPC_FAULT_CODE_ACCESS_ERROR = 4
|
||||
|
||||
# 0 to 31, excluding tab, newline, and carriage return
|
||||
CONTROL_CHARACTERS = dict.fromkeys(set(range(32)) - {9, 10, 13})
|
||||
|
||||
|
||||
def xmlrpc_handle_exception_int(e):
|
||||
if isinstance(e, odoo.exceptions.RedirectWarning):
|
||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
elif isinstance(e, odoo.exceptions.AccessError):
|
||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e))
|
||||
elif isinstance(e, odoo.exceptions.AccessDenied):
|
||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e))
|
||||
elif isinstance(e, odoo.exceptions.UserError):
|
||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
else:
|
||||
info = sys.exc_info()
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
fault = xmlrpc.client.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
||||
|
||||
return dumps(fault)
|
||||
|
||||
|
||||
def xmlrpc_handle_exception_string(e):
|
||||
if isinstance(e, odoo.exceptions.RedirectWarning):
|
||||
fault = xmlrpc.client.Fault(f'warning -- Warning\n\n{e}', '')
|
||||
elif isinstance(e, odoo.exceptions.MissingError):
|
||||
fault = xmlrpc.client.Fault(f'warning -- MissingError\n\n{e}', '')
|
||||
elif isinstance(e, odoo.exceptions.AccessError):
|
||||
fault = xmlrpc.client.Fault(f'warning -- AccessError\n\n{e}', '')
|
||||
elif isinstance(e, odoo.exceptions.AccessDenied):
|
||||
fault = xmlrpc.client.Fault('AccessDenied', str(e))
|
||||
elif isinstance(e, odoo.exceptions.UserError):
|
||||
fault = xmlrpc.client.Fault(f'warning -- UserError\n\n{e}', '')
|
||||
# InternalError
|
||||
else:
|
||||
info = sys.exc_info()
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
fault = xmlrpc.client.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
||||
|
||||
return dumps(fault)
|
||||
|
||||
|
||||
class OdooMarshaller(xmlrpc.client.Marshaller):
|
||||
dispatch = dict(xmlrpc.client.Marshaller.dispatch)
|
||||
|
||||
def dump_frozen_dict(self, value, write):
|
||||
value = dict(value)
|
||||
self.dump_struct(value, write)
|
||||
|
||||
# By default, in xmlrpc, bytes are converted to xmlrpc.client.Binary object.
|
||||
# Historically, odoo is sending binary as base64 string.
|
||||
# In python 3, base64.b64{de,en}code() methods now works on bytes.
|
||||
def dump_bytes(self, value, write):
|
||||
self.dump_unicode(value.decode(), write)
|
||||
|
||||
def dump_datetime(self, value, write):
|
||||
# override to marshall as a string for backwards compatibility
|
||||
value = Datetime.to_string(value)
|
||||
self.dump_unicode(value, write)
|
||||
|
||||
# convert date objects to strings in iso8061 format.
|
||||
def dump_date(self, value, write):
|
||||
value = Date.to_string(value)
|
||||
self.dump_unicode(value, write)
|
||||
|
||||
def dump_lazy(self, value, write):
|
||||
v = value._value
|
||||
return self.dispatch[type(v)](self, v, write)
|
||||
|
||||
def dump_unicode(self, value, write):
|
||||
# XML 1.0 disallows control characters, remove them otherwise they break clients
|
||||
return super().dump_unicode(value.translate(CONTROL_CHARACTERS), write)
|
||||
|
||||
dispatch[frozendict] = dump_frozen_dict
|
||||
dispatch[bytes] = dump_bytes
|
||||
dispatch[datetime] = dump_datetime
|
||||
dispatch[date] = dump_date
|
||||
dispatch[lazy] = dump_lazy
|
||||
dispatch[str] = dump_unicode
|
||||
dispatch[Command] = dispatch[int]
|
||||
dispatch[defaultdict] = dispatch[dict]
|
||||
dispatch[Markup] = lambda self, value, write: self.dispatch[str](self, str(value), write)
|
||||
|
||||
|
||||
def dumps(params: list | tuple | xmlrpc.client.Fault) -> str:
|
||||
response = OdooMarshaller(allow_none=False).dumps(params)
|
||||
return f"""\
|
||||
<?xml version="1.0"?>
|
||||
<methodResponse>
|
||||
{response}
|
||||
</methodResponse>
|
||||
"""
|
||||
|
||||
# ==========================================================
|
||||
# RPC Controller
|
||||
# ==========================================================
|
||||
|
||||
|
||||
class XMLRPC(Controller):
|
||||
"""Handle RPC connections."""
|
||||
|
||||
def _xmlrpc(self, service):
|
||||
"""Common method to handle an XML-RPC request."""
|
||||
data = request.httprequest.get_data()
|
||||
params, method = xmlrpc.client.loads(data, use_datetime=True)
|
||||
result = dispatch_rpc(service, method, params)
|
||||
return dumps((result,))
|
||||
|
||||
@route("/xmlrpc/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
||||
def xmlrpc_1(self, service):
|
||||
"""XML-RPC service that returns faultCode as strings.
|
||||
|
||||
This entrypoint is historical and non-compliant, but kept for
|
||||
backwards-compatibility.
|
||||
"""
|
||||
logger.warning(RPC_DEPRECATION_NOTICE, __name__)
|
||||
_check_request()
|
||||
try:
|
||||
response = self._xmlrpc(service)
|
||||
except Exception as error:
|
||||
error.error_response = Response(
|
||||
response=xmlrpc_handle_exception_string(error),
|
||||
mimetype='text/xml',
|
||||
)
|
||||
raise
|
||||
return Response(response=response, mimetype='text/xml')
|
||||
|
||||
@route("/xmlrpc/2/<service>", auth="none", methods=["POST"], csrf=False, save_session=False)
|
||||
def xmlrpc_2(self, service):
|
||||
"""XML-RPC service that returns faultCode as int."""
|
||||
logger.warning(RPC_DEPRECATION_NOTICE, __name__)
|
||||
_check_request()
|
||||
try:
|
||||
response = self._xmlrpc(service)
|
||||
except Exception as error:
|
||||
error.error_response = Response(
|
||||
response=xmlrpc_handle_exception_int(error),
|
||||
mimetype='text/xml',
|
||||
)
|
||||
raise
|
||||
return Response(response=response, mimetype='text/xml')
|
||||
1
odoo-bringout-oca-ocb-rpc/rpc/tests/__init__.py
Normal file
1
odoo-bringout-oca-ocb-rpc/rpc/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import test_xmlrpc
|
||||
295
odoo-bringout-oca-ocb-rpc/rpc/tests/test_xmlrpc.py
Normal file
295
odoo-bringout-oca-ocb-rpc/rpc/tests/test_xmlrpc.py
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import collections
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import odoo
|
||||
import odoo.tools
|
||||
from odoo.exceptions import AccessDenied, AccessError
|
||||
from odoo.http import _request_stack
|
||||
from odoo.service import common as auth
|
||||
from odoo.service import model
|
||||
from odoo.tests import common
|
||||
from odoo.tools import DotDict, mute_logger
|
||||
|
||||
from odoo.addons.base.tests.common import SavepointCaseWithUserDemo
|
||||
|
||||
|
||||
class TestExternalAPI(SavepointCaseWithUserDemo):
|
||||
|
||||
def test_call_kw(self):
|
||||
"""kwargs is not modified by the execution of the call"""
|
||||
partner = self.env['res.partner'].create({'name': 'MyPartner1'})
|
||||
args = (partner.ids, ['name'])
|
||||
kwargs = {'context': {'test': True}}
|
||||
model.call_kw(self.env['res.partner'], 'read', args, kwargs)
|
||||
self.assertEqual(kwargs, {'context': {'test': True}})
|
||||
|
||||
|
||||
@common.tagged('post_install', '-at_install')
|
||||
class TestXMLRPC(common.HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestXMLRPC, self).setUp()
|
||||
self.admin_uid = self.env.ref('base.user_admin').id
|
||||
|
||||
ml_xml = mute_logger('odoo.addons.rpc.controllers.xmlrpc')
|
||||
ml_xml.__enter__() # noqa: PLC2801
|
||||
self.addCleanup(ml_xml.__exit__)
|
||||
|
||||
ml_json = mute_logger('odoo.addons.rpc.controllers.jsonrpc')
|
||||
ml_json.__enter__() # noqa: PLC2801
|
||||
self.addCleanup(ml_json.__exit__)
|
||||
|
||||
def xmlrpc(self, model, method, *args, **kwargs):
|
||||
return self.xmlrpc_object.execute_kw(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
model, method, args, kwargs
|
||||
)
|
||||
|
||||
def test_01_xmlrpc_login(self):
|
||||
""" Try to login on the common service. """
|
||||
db_name = common.get_db_name()
|
||||
uid = self.xmlrpc_common.login(db_name, 'admin', 'admin')
|
||||
self.assertEqual(uid, self.admin_uid)
|
||||
|
||||
def test_xmlrpc_ir_model_search(self):
|
||||
""" Try a search on the object service. """
|
||||
o = self.xmlrpc_object
|
||||
db_name = common.get_db_name()
|
||||
ids = o.execute(db_name, self.admin_uid, 'admin', 'ir.model', 'search', [])
|
||||
self.assertIsInstance(ids, list)
|
||||
ids = o.execute(db_name, self.admin_uid, 'admin', 'ir.model', 'search', [], {})
|
||||
self.assertIsInstance(ids, list)
|
||||
|
||||
def test_xmlrpc_datetime(self):
|
||||
""" Test that native datetime can be sent over xmlrpc
|
||||
"""
|
||||
m = self.env.ref('base.model_res_device_log')
|
||||
self.env['ir.model.access'].create({
|
||||
'name': "w/e",
|
||||
'model_id': m.id,
|
||||
'perm_read': True,
|
||||
'perm_create': True,
|
||||
})
|
||||
|
||||
now = datetime.datetime.now()
|
||||
ids = self.xmlrpc(
|
||||
'res.device.log', 'create',
|
||||
{'session_identifier': "abc", 'first_activity': now, 'revoked': False}
|
||||
)
|
||||
[r] = self.xmlrpc(
|
||||
'res.device.log', 'read',
|
||||
ids, ['first_activity'],
|
||||
)
|
||||
self.assertEqual(r['first_activity'], now.isoformat(" ", "seconds"))
|
||||
|
||||
def test_xmlrpc_read_group(self):
|
||||
self.xmlrpc_object.execute(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
'res.partner', 'formatted_read_group', [], ['parent_id'], ['color:sum'],
|
||||
)
|
||||
|
||||
def test_xmlrpc_name_search(self):
|
||||
self.xmlrpc_object.execute(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
'res.partner', 'name_search', "admin"
|
||||
)
|
||||
|
||||
def test_xmlrpc_html_field(self):
|
||||
sig = '<p>bork bork bork <span style="font-weight: bork">bork</span><br></p>'
|
||||
r = self.env['res.users'].create({
|
||||
'name': 'bob',
|
||||
'login': 'bob',
|
||||
'signature': sig
|
||||
})
|
||||
self.assertEqual(str(r.signature), sig)
|
||||
[x] = self.xmlrpc('res.users', 'read', r.id, ['signature'])
|
||||
self.assertEqual(x['signature'], sig)
|
||||
|
||||
def test_xmlrpc_frozendict_marshalling(self):
|
||||
""" Test that the marshalling of a frozendict object works properly over XMLRPC """
|
||||
self.env.ref('base.user_admin').tz = "Europe/Brussels"
|
||||
ctx = self.xmlrpc_object.execute(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
'res.users', 'context_get',
|
||||
)
|
||||
self.assertEqual(ctx['lang'], 'en_US')
|
||||
self.assertEqual(ctx['tz'], 'Europe/Brussels')
|
||||
|
||||
def test_xmlrpc_defaultdict_marshalling(self):
|
||||
"""
|
||||
Test that the marshalling of a collections.defaultdict object
|
||||
works properly over XMLRPC
|
||||
"""
|
||||
self.patch(self.registry['res.users'], 'context_get',
|
||||
odoo.api.model(lambda *_: collections.defaultdict(int)))
|
||||
self.assertEqual(self.xmlrpc('res.users', 'context_get'), {})
|
||||
|
||||
def test_xmlrpc_remove_control_characters(self):
|
||||
record = self.env['res.users'].create({
|
||||
'name': 'bob with a control character: \x03',
|
||||
'login': 'bob',
|
||||
})
|
||||
self.assertEqual(record.name, 'bob with a control character: \x03')
|
||||
[record_data] = self.xmlrpc('res.users', 'read', record.id, ['name'])
|
||||
self.assertEqual(record_data['name'], 'bob with a control character: ')
|
||||
|
||||
def test_jsonrpc_read_group(self):
|
||||
self._json_call(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
'res.partner', 'formatted_read_group', [], ['parent_id'], ['color:sum'],
|
||||
)
|
||||
|
||||
def test_jsonrpc_name_search(self):
|
||||
# well that's some sexy sexy call right there
|
||||
self._json_call(
|
||||
common.get_db_name(),
|
||||
self.admin_uid, 'admin',
|
||||
'res.partner', 'name_search', 'admin'
|
||||
)
|
||||
|
||||
def _json_call(self, *args):
|
||||
self.url_open(f"{self.base_url()}/jsonrpc", json={
|
||||
'jsonrpc': '2.0',
|
||||
'id': None,
|
||||
'method': 'call',
|
||||
'params': {
|
||||
'service': 'object',
|
||||
'method': 'execute',
|
||||
'args': args
|
||||
}
|
||||
})
|
||||
|
||||
def test_xmlrpc_attachment_raw(self):
|
||||
ids = self.env['ir.attachment'].create({'name': 'n', 'raw': b'\x01\x09'}).ids
|
||||
[att] = self.xmlrpc_object.execute(
|
||||
common.get_db_name(), self.admin_uid, 'admin',
|
||||
'ir.attachment', 'read', ids, ['raw'])
|
||||
self.assertEqual(att['raw'], '\t',
|
||||
"on read, binary data should be decoded as a string and stripped from control character")
|
||||
|
||||
# really just for the test cursor
|
||||
@common.tagged('post_install', '-at_install')
|
||||
class TestAPIKeys(common.HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._user = cls.env['res.users'].create({
|
||||
'name': "Bylan",
|
||||
'login': 'byl',
|
||||
'password': 'ananananan',
|
||||
'tz': 'Australia/Eucla',
|
||||
})
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def get_json_data():
|
||||
raise ValueError("There is no json here")
|
||||
# needs a fake request in order to call methods protected with check_identity
|
||||
self.http_request_key = self.canonical_tag
|
||||
fake_req = DotDict({
|
||||
# various things go and access request items
|
||||
'httprequest': DotDict({
|
||||
'environ': {'REMOTE_ADDR': 'localhost'},
|
||||
'cookies': {common.TEST_CURSOR_COOKIE_NAME: self.canonical_tag},
|
||||
'args': {},
|
||||
}),
|
||||
'cookies': {common.TEST_CURSOR_COOKIE_NAME: self.canonical_tag},
|
||||
# bypass check_identity flow
|
||||
'session': {'identity-check-last': time.time()},
|
||||
'geoip': {},
|
||||
'get_json_data': get_json_data,
|
||||
})
|
||||
_request_stack.push(fake_req)
|
||||
self.addCleanup(_request_stack.pop)
|
||||
|
||||
def test_trivial(self):
|
||||
uid = auth.dispatch('authenticate', [self.env.cr.dbname, 'byl', 'ananananan', {}])
|
||||
self.assertEqual(uid, self._user.id)
|
||||
|
||||
ctx = model.dispatch('execute_kw', [
|
||||
self.env.cr.dbname, uid, 'ananananan',
|
||||
'res.users', 'context_get', []
|
||||
])
|
||||
self.assertEqual(ctx['tz'], 'Australia/Eucla')
|
||||
|
||||
def test_wrongpw(self):
|
||||
# User.authenticate raises but RPC.authenticate returns False
|
||||
uid = auth.dispatch('authenticate', [self.env.cr.dbname, 'byl', 'aws', {}])
|
||||
self.assertFalse(uid)
|
||||
with self.assertRaises(AccessDenied):
|
||||
model.dispatch('execute_kw', [
|
||||
self.env.cr.dbname, self._user.id, 'aws',
|
||||
'res.users', 'context_get', []
|
||||
])
|
||||
|
||||
def test_key(self):
|
||||
env = self.env(user=self._user)
|
||||
r = env['res.users.apikeys.description'].create({
|
||||
'name': 'a',
|
||||
}).make_key()
|
||||
k = r['context']['default_key']
|
||||
|
||||
uid = auth.dispatch('authenticate', [self.env.cr.dbname, 'byl', 'ananananan', {}])
|
||||
self.assertEqual(uid, self._user.id)
|
||||
|
||||
uid = auth.dispatch('authenticate', [self.env.cr.dbname, 'byl', k, {}])
|
||||
self.assertEqual(uid, self._user.id)
|
||||
|
||||
ctx = model.dispatch('execute_kw', [
|
||||
self.env.cr.dbname, uid, k,
|
||||
'res.users', 'context_get', []
|
||||
])
|
||||
self.assertEqual(ctx['tz'], 'Australia/Eucla')
|
||||
|
||||
api_key = model.call_kw(
|
||||
model=self.env['res.users.apikeys.description'],
|
||||
name='create',
|
||||
args=[{'name': 'Name of the key'}],
|
||||
kwargs={}
|
||||
)
|
||||
self.assertTrue(isinstance(api_key, int))
|
||||
|
||||
def test_delete(self):
|
||||
env = self.env(user=self._user)
|
||||
env['res.users.apikeys.description'].create({'name': 'b',}).make_key()
|
||||
env['res.users.apikeys.description'].create({'name': 'b',}).make_key()
|
||||
env['res.users.apikeys.description'].create({'name': 'b',}).make_key()
|
||||
k0, k1, k2 = env['res.users.apikeys'].search([])
|
||||
|
||||
# user can remove their own keys
|
||||
k0.remove()
|
||||
self.assertFalse(k0.exists())
|
||||
|
||||
# admin can remove user keys
|
||||
k1.with_user(self.env.ref('base.user_admin')).remove ()
|
||||
self.assertFalse(k1.exists())
|
||||
|
||||
# other user can't remove user keys
|
||||
u = self.env['res.users'].create({
|
||||
'name': 'a',
|
||||
'login': 'a',
|
||||
'group_ids': self.env.ref('base.group_user').ids,
|
||||
})
|
||||
with self.assertRaises(AccessError):
|
||||
k2.with_user(u).remove()
|
||||
|
||||
def test_disabled(self):
|
||||
env = self.env(user=self._user)
|
||||
k = env['res.users.apikeys.description'].create({'name': 'b',}).make_key()['context']['default_key']
|
||||
|
||||
self._user.active = False
|
||||
|
||||
with self.assertRaises(AccessDenied):
|
||||
model.dispatch('execute_kw', [
|
||||
self.env.cr.dbname, self._user.id, 'ananananan',
|
||||
'res.users', 'context_get', []
|
||||
])
|
||||
|
||||
with self.assertRaises(AccessDenied):
|
||||
model.dispatch('execute_kw', [
|
||||
self.env.cr.dbname, self._user.id, k,
|
||||
'res.users', 'context_get', []
|
||||
])
|
||||
Loading…
Add table
Add a link
Reference in a new issue