Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Copyright (C) 2025 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class CxTowerWebhookEvalMixin(models.AbstractModel):
|
||||
_name = "cx.tower.webhook.eval.mixin"
|
||||
_inherit = [
|
||||
"cx.tower.template.mixin",
|
||||
"cx.tower.key.mixin",
|
||||
"cx.tower.yaml.mixin",
|
||||
"cx.tower.reference.mixin",
|
||||
]
|
||||
_description = "Eval context/code helper for Cetmix Tower Webhook"
|
||||
|
||||
code_help = fields.Html(
|
||||
compute="_compute_code_help",
|
||||
default=lambda self: self._default_eval_code_help(),
|
||||
compute_sudo=True,
|
||||
)
|
||||
code = fields.Text(
|
||||
default=lambda self: self._default_eval_code(),
|
||||
required=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_depends_fields(cls):
|
||||
"""Add code to the depends fields."""
|
||||
return ["code"]
|
||||
|
||||
def _compute_code_help(self):
|
||||
"""
|
||||
Compute code help
|
||||
"""
|
||||
self.code_help = self._default_eval_code_help()
|
||||
|
||||
def _default_eval_code_help(self):
|
||||
"""
|
||||
Return the default code help text for webhook or authenticator.
|
||||
|
||||
We use default because the computation method for this field
|
||||
would not be triggered before this record is saved. And we need
|
||||
to show the value instantly.
|
||||
|
||||
Returns:
|
||||
str: HTML-formatted help string containing available objects and libraries.
|
||||
"""
|
||||
available_libraries = self._get_python_eval_odoo_objects()
|
||||
available_libraries.update(self._get_python_eval_libraries())
|
||||
help_text_fragments = []
|
||||
for key, value in available_libraries.items():
|
||||
if key == "server":
|
||||
# Server is not available in the webhook/authenticator eval context
|
||||
continue
|
||||
help_text_fragments.append(f"<li><code>{key}</code>: {value['help']}</li>")
|
||||
|
||||
help_text = "<ul>" + "".join(help_text_fragments) + "</ul>"
|
||||
return f"{self._get_default_python_eval_code_help()}{help_text}"
|
||||
|
||||
def _get_python_eval_odoo_objects(self, **kwargs):
|
||||
"""
|
||||
Return Odoo objects available in the eval context.
|
||||
|
||||
Args:
|
||||
**kwargs: Optional context values.
|
||||
|
||||
Returns:
|
||||
dict: Mapping of object names to their import values and help.
|
||||
"""
|
||||
return self.env["cx.tower.command"]._get_python_command_odoo_objects()
|
||||
|
||||
def _get_python_eval_libraries(self):
|
||||
"""
|
||||
Return Python libraries available in the eval context.
|
||||
|
||||
Returns:
|
||||
dict: Mapping of library names to their import values and help.
|
||||
"""
|
||||
return self.env["cx.tower.command"]._get_python_command_libraries()
|
||||
|
||||
def _get_default_python_eval_code_help(self):
|
||||
"""
|
||||
Return the default help text for eval code.
|
||||
|
||||
Returns:
|
||||
str: Help text.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def _default_eval_code(self):
|
||||
"""
|
||||
Return the default code for webhook or authenticator.
|
||||
|
||||
Returns:
|
||||
str: Default Python code.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def _prepare_webhook_eval_context(self, context_extra=None, default_result=None):
|
||||
"""
|
||||
Build the evaluation context for webhook or authenticator
|
||||
safe_eval.
|
||||
|
||||
Args:
|
||||
context_extra (dict): Additional context variables
|
||||
(payload, headers, etc).
|
||||
default_result (dict): Default value for the 'result' variable.
|
||||
|
||||
Returns:
|
||||
dict: Prepared eval context.
|
||||
"""
|
||||
context_extra = context_extra or {}
|
||||
# Get the Odoo objects first
|
||||
imports = self._get_python_eval_odoo_objects(**context_extra)
|
||||
|
||||
# Update with the libraries
|
||||
imports.update(self._get_python_eval_libraries())
|
||||
eval_context = {key: value["import"] for key, value in imports.items()}
|
||||
|
||||
# Remove server from eval context
|
||||
eval_context.pop("server", None)
|
||||
|
||||
# Set default result
|
||||
default_result = default_result or {}
|
||||
eval_context["result"] = default_result.copy()
|
||||
|
||||
return eval_context
|
||||
|
||||
def _run_webhook_eval_code(self, code, **kwargs):
|
||||
"""
|
||||
Helper to execute user code safely. Returns the 'result' variable from context.
|
||||
|
||||
Args:
|
||||
code (str): User code to run
|
||||
kwargs:
|
||||
key (dict): Extra keys for secret parser
|
||||
context_extra (dict): Extra context variables (payload, headers, etc)
|
||||
default_result (dict): Default value for the 'result' variable
|
||||
|
||||
Returns:
|
||||
dict: The 'result' variable from context
|
||||
"""
|
||||
eval_context = self._prepare_webhook_eval_context(**kwargs)
|
||||
|
||||
if not code:
|
||||
# if code is empty, return the default result
|
||||
return eval_context["result"]
|
||||
|
||||
# prepare the code for evaluation
|
||||
code_and_secrets = self.env["cx.tower.key"]._parse_code_and_return_key_values(
|
||||
code, pythonic_mode=True, **kwargs.get("key", {})
|
||||
)
|
||||
secrets = code_and_secrets.get("key_values")
|
||||
webhook_code = code_and_secrets["code"]
|
||||
|
||||
code = self.env["cx.tower.key"]._parse_code(
|
||||
webhook_code, pythonic_mode=True, **kwargs.get("key", {})
|
||||
)
|
||||
|
||||
# execute user code
|
||||
safe_eval(
|
||||
code,
|
||||
eval_context,
|
||||
mode="exec",
|
||||
nocopy=True,
|
||||
)
|
||||
result = eval_context["result"]
|
||||
return self._parse_eval_code_result(result, secrets=secrets, **kwargs)
|
||||
|
||||
def _parse_eval_code_result(self, result, secrets=None, **kwargs):
|
||||
"""
|
||||
Post-processes the result returned from webhook/authenticator eval code.
|
||||
|
||||
If 'secrets' are provided, all occurrences of secret values in the
|
||||
'message' field of result will be replaced with a spoiler string to
|
||||
prevent sensitive information leakage.
|
||||
|
||||
Args:
|
||||
result (dict): The dict returned from the executed eval code,
|
||||
expected to have at least a 'message' key.
|
||||
secrets (dict, optional): A mapping of secret key-value
|
||||
pairs used for replacement in 'message'.
|
||||
|
||||
Returns:
|
||||
dict: The processed result with secrets masked in the 'message'
|
||||
field, if applicable.
|
||||
"""
|
||||
if not isinstance(result, dict):
|
||||
raise ValidationError(
|
||||
_("Webhook/Authenticator code error: result is not a dict")
|
||||
)
|
||||
|
||||
if secrets and isinstance(result.get("message"), str):
|
||||
result["message"] = self.env["cx.tower.key"]._replace_with_spoiler(
|
||||
result["message"], secrets
|
||||
)
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user